├── .gitignore
├── .vscode
├── launch.json
└── settings.json
├── README.md
├── babel.config.js
├── example
├── apiInject
│ ├── App.js
│ └── index.html
├── componentEmit
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentSlot
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentUpdate
│ ├── App.js
│ ├── Child.js
│ ├── index.html
│ └── main.js
├── currentInstance
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── customRenderer
│ ├── App.js
│ ├── index.html
│ └── main.js
├── helloworld
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── nextTick
│ ├── App.js
│ ├── index.html
│ └── main.js
├── patchChildren
│ ├── App.js
│ ├── ArrayToArray.js
│ ├── ArrayToText.js
│ ├── TextToArray.js
│ ├── TextToText.js
│ ├── index.html
│ └── main.js
└── update
│ ├── App.js
│ ├── index.html
│ └── main.js
├── lib
├── guide-mini-vue.cjs.js
└── guide-mini-vue.esm.js
├── package.json
├── rollup.config.js
├── src
├── compiler-core
│ ├── src
│ │ ├── ast.ts
│ │ ├── codegen.ts
│ │ ├── parse.ts
│ │ └── transform.ts
│ └── tests
│ │ ├── __snapshots__
│ │ └── codegen.spec.ts.snap
│ │ ├── codegen.spec.ts
│ │ ├── parse.spec.ts
│ │ └── transform.spec.ts
├── 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
│ ├── apiInject.ts
│ ├── component.ts
│ ├── componentEmit.ts
│ ├── componentProps.ts
│ ├── componentPublicInstance.ts
│ ├── componentSlots.ts
│ ├── componentUpdateUtils.ts
│ ├── createApp.ts
│ ├── h.ts
│ ├── helpers
│ │ └── renderSlots.ts
│ ├── index.ts
│ ├── renderer.ts
│ ├── scheduler.ts
│ └── vnode.ts
├── runtime-dom
│ └── index.ts
└── shared
│ ├── index.ts
│ └── shapeFlags.ts
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies npm包文件
2 | /node_modules
3 |
4 | # production 打包文件
5 | /dist
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "name": "vscode-jest-tests",
10 | "request": "launch",
11 | "args": ["--runInBand", "--watchAll=false"],
12 | "cwd": "${workspaceFolder}",
13 | "console": "integratedTerminal",
14 | "internalConsoleOptions": "neverOpen",
15 | "disableOptimisticBPs": true,
16 | "program": "${workspaceFolder}/node_modules/.bin/jest",
17 | "windows": {
18 | "program": "${workspaceFolder}/node_modules/jest/bin/jest"
19 | }
20 | },
21 | {
22 | "name": "Debug Jest Tests",
23 | "type": "node",
24 | "request": "launch",
25 | "runtimeArgs": [
26 | "--inspect-brk",
27 | "${workspaceRoot}/node_modules/jest/bin/jest.js",
28 | "--runInBand"
29 | ],
30 | //"sourceMaps": true,
31 | "trace": true,
32 | "console": "integratedTerminal",
33 | "internalConsoleOptions": "neverOpen",
34 | "port": 9229
35 | }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "liveServer.settings.port": 5503,
3 | "cSpell.words": [
4 | "vnode"
5 | ],
6 | "editor.fontSize": 16,
7 | "editor.lineHeight": 24
8 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **guide-mini-vue**
2 | ===
3 | **vue3**的简单实现,学习参考 **mini-vue**
4 |
5 | 目前已实现功能:
6 |
7 | + [x] effect、reactive、依赖收集、依赖触发
8 | + [x] effect 的 scheduler 功能
9 | + [x] readonly 功能
10 | + [x] isReactive、isReadonly 工具函数
11 | + [x] stop 功能
12 | + [x] reactive、readonly 的对象嵌套
13 | + [x] shallowReadonly 功能
14 | + [x] isProxy 工具函数
15 | + [x] ref 功能
16 | + [x] isRef、unRef 工具函数
17 | + [x] 实现 proxyRefs 功能
18 | + [x] 实现 computed 计算属性
19 | + [x] 实现初始化 component 主流程
20 | + [x] 实现组件代理对象
21 | + [x] 实现 shapeFlags
22 | + [x] 实现注册事件功能
23 | + [x] 实现组件 props 功能
24 | + [x] 实现组件 emit 功能
25 | + [x] 实现组件 slots 功能
26 | + [x] 实现 Fragment和Text类型 节点功能
27 | + [x] 实现 getCurrentInstance
28 | + [x] 实现 provide & inject 功能
29 | + [x] 实现 自定义渲染器 功能
30 | + [x] 更新 element 流程搭建
31 | + [x] 更新 element 的 props
32 | + [x] 更新 element 的 children
33 | + [x] 实现 双端对比 diff 算法
34 | + [x] 实现 组件更新功能
35 | + [x] 实现 nextTick 功能
36 | + [x] 实现 解析插值 功能
37 | + [x] 实现 解析element 功能
38 | + [x] 实现 解析text 功能
39 | + [x] 实现 解析三种联合类型 功能
40 | + [x] 实现 transform 功能
41 | + [x] 实现 代码生成 string 类型
42 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', {
4 | targets: {
5 | node: 'current'
6 | }
7 | }],
8 | '@babel/preset-typescript',
9 | ],
10 |
11 | };
--------------------------------------------------------------------------------
/example/apiInject/App.js:
--------------------------------------------------------------------------------
1 | import {h,provide,inject} from '../../lib/guide-mini-vue.esm.js';
2 |
3 | const Provider = {
4 | name:"Provider",
5 | setup(){
6 | provide("foo","fooVal");
7 | provide("bar","barVal");
8 | },
9 | render(){
10 | return h("div", {}, [h("p", {}, "Provider"), h(ProviderTwo)])
11 | }
12 | }
13 | const ProviderTwo = {
14 | name: "ProviderTwo",
15 | setup() {
16 | provide("foo",'fooTwo')
17 | const foo = inject("foo");
18 | return {
19 | foo
20 | }
21 | },
22 | render() {
23 | return h("div", {}, [h("p", {}, `ProviderTwo foo ${this.foo}`), h(Consumer)])
24 | }
25 | }
26 | const Consumer = {
27 | name: "Consumer",
28 | setup(){
29 | const foo = inject("foo");
30 | const bar = inject("bar");
31 | const baz = inject("baz",()=>'bazDefault');
32 | return {
33 | foo,
34 | bar,
35 | baz
36 | }
37 | },
38 | render(){
39 | return h("div", {}, `Consumer:-${this.foo} - ${this.bar}- ${this.baz}`)
40 | }
41 | }
42 | export default {
43 | name:'App',
44 | render(){
45 | return h("div", {}, [h("p", {}, "apiInject"), h(Provider)])
46 | },
47 | setup(){
48 |
49 | }
50 | }
--------------------------------------------------------------------------------
/example/apiInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
12 |
13 |
14 |
15 |
22 |
23 |
--------------------------------------------------------------------------------
/example/componentEmit/App.js:
--------------------------------------------------------------------------------
1 | import {h} from '../../lib/guide-mini-vue.esm.js';
2 | import {Foo} from './Foo.js'
3 | export const App = {
4 | name: 'App',
5 | render(){
6 | return h("div",{},[h("div",{},"App"),h(Foo,{
7 | onAdd(a,b){
8 | console.log('onAdd',a,b);
9 | },
10 | onBarFoo(c,d){
11 | console.log('onBarFoo', c, d);
12 | }
13 | })])
14 | },
15 | setup(){
16 | return {
17 |
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/example/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import {h} from '../../lib/guide-mini-vue.esm.js';
2 | export const Foo = {
3 | setup(props,{emit}){
4 | const emitAdd = ()=>{
5 | console.log("emit add")
6 | emit("add",1,2)
7 | emit("bar-foo",3,4)
8 | }
9 | return {emitAdd}
10 | },
11 | render(){
12 | const btn = h("button",{
13 | onClick:this.emitAdd
14 | },"emitAdd")
15 | const foo = h("p",{},"foo");
16 | return h("div",{},[foo,btn])
17 | }
18 | }
--------------------------------------------------------------------------------
/example/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import {createApp} from '../../lib/guide-mini-vue.esm.js'
2 | import {App} from './App.js'
3 | const rootContainer = document.querySelector('#app');
4 | createApp(App).mount(rootContainer)
--------------------------------------------------------------------------------
/example/componentSlot/App.js:
--------------------------------------------------------------------------------
1 | import {h,createTextVnode} from '../../lib/guide-mini-vue.esm.js';
2 | import {Foo} from './Foo.js'
3 | export const App = {
4 | name:'App',
5 | render(){
6 | const app = h("div",{},"App");
7 | const foo = h(Foo, {},
8 | {
9 | header:({age})=>[h("p", {}, "header"+age),createTextVnode('你好呀')],
10 | footer:()=>h("p", {}, "footer")
11 | }
12 | );
13 | //const foo = h(Foo, {}, h("p", {}, "123"));
14 | return h("div",{},[app,foo])
15 | },
16 | setup(){
17 | return {}
18 | }
19 | }
--------------------------------------------------------------------------------
/example/componentSlot/Foo.js:
--------------------------------------------------------------------------------
1 | import {h,renderSlots} from '../../lib/guide-mini-vue.esm.js';
2 | export const Foo = {
3 | setup(){
4 | return {}
5 | },
6 | render(){
7 | const foo = h("p",{},"foo");
8 | const age = 11;
9 | return h("div", {}, [
10 | renderSlots(this.$slots, "header",{age}) ,
11 | foo,
12 | renderSlots(this.$slots, "footer",{})
13 | ]);
14 | }
15 | }
--------------------------------------------------------------------------------
/example/componentSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/componentSlot/main.js:
--------------------------------------------------------------------------------
1 | import {createApp} from '../../lib/guide-mini-vue.esm.js'
2 | import {App} from './App.js'
3 | const rootContainer = document.querySelector('#app');
4 | createApp(App).mount(rootContainer)
--------------------------------------------------------------------------------
/example/componentUpdate/App.js:
--------------------------------------------------------------------------------
1 | import {h,ref} from '../../lib/guide-mini-vue.esm.js'
2 | import Child from './Child.js';
3 | export const App = {
4 | name:'App',
5 | setup(){
6 | const msg = ref('123');
7 | const count = ref(1);
8 | window.msg = msg;
9 |
10 | const changeChildProps = ()=>{
11 | msg.value = '456'
12 | }
13 | const changeCount = ()=>{
14 | count.value++;
15 | }
16 | return {msg,count,changeChildProps,changeCount}
17 | },
18 | render(){
19 | return h("div",{},[
20 | h("div",{},'你好'),
21 | h(
22 | "button",
23 | {
24 | onClick: this.changeChildProps
25 | },
26 | "change child props"
27 | ),
28 | h(
29 | Child,{
30 | msg:this.msg
31 | }
32 | ),
33 | h(
34 | "button",
35 | {
36 | onClick: this.changeCount
37 | },
38 | "change self count"
39 | ),
40 | h(
41 | "p",{},"count"+this.count
42 | )
43 | ])
44 | }
45 | }
--------------------------------------------------------------------------------
/example/componentUpdate/Child.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/guide-mini-vue.esm.js";
2 | export default {
3 | name: "Child",
4 | setup(props, { emit }) {},
5 | render() {
6 | console.log(this.$props)
7 | return h("div", {}, [h("div", {}, "child" + this.$props.msg)]);
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/example/componentUpdate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/componentUpdate/main.js:
--------------------------------------------------------------------------------
1 | import {createApp} from '../../lib/guide-mini-vue.esm.js'
2 | import {App} from './App.js'
3 | const rootContainer = document.querySelector('#app');
4 | createApp(App).mount(rootContainer)
--------------------------------------------------------------------------------
/example/currentInstance/App.js:
--------------------------------------------------------------------------------
1 | import {h,getCurrentInstance} from '../../lib/guide-mini-vue.esm.js';
2 | import {Foo} from './Foo.js'
3 | export const App = {
4 | name:'App',
5 | render(){
6 | return h("div",{},[h("p",{},"currentInstance Demo"),h(Foo)])
7 | },
8 | setup(){
9 | const instance = getCurrentInstance();
10 | console.log('App:', instance)
11 | return {}
12 | }
13 | }
--------------------------------------------------------------------------------
/example/currentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import {h,getCurrentInstance} from '../../lib/guide-mini-vue.esm.js';
2 | export const Foo = {
3 | name:"Foo",
4 | setup(){
5 | const instance = getCurrentInstance();
6 | console.log('foo',instance)
7 | return {}
8 | },
9 | render(){
10 | return h("div", {}, "Foo");
11 | }
12 | }
--------------------------------------------------------------------------------
/example/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/currentInstance/main.js:
--------------------------------------------------------------------------------
1 | import {createApp} from '../../lib/guide-mini-vue.esm.js'
2 | import {App} from './App.js'
3 | const rootContainer = document.querySelector('#app');
4 | createApp(App).mount(rootContainer)
--------------------------------------------------------------------------------
/example/customRenderer/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js"
2 |
3 | export const App = {
4 | setup(){
5 | return{
6 | x:100,
7 | y:100
8 | }
9 | },
10 | render(){
11 | return h("rect",{x:this.x,y:this.y});
12 | }
13 | }
--------------------------------------------------------------------------------
/example/customRenderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/example/customRenderer/main.js:
--------------------------------------------------------------------------------
1 | import {createRenderer} from '../../lib/guide-mini-vue.esm.js';
2 | import {App} from './App.js';
3 | console.log(PIXI)
4 |
5 | const game = new PIXI.Application({
6 | width:500,
7 | height:500
8 | })
9 | document.body.append(game.view);
10 |
11 |
12 | const renderer = createRenderer({
13 | createElement(type){
14 | if(type==='rect'){
15 | const rect = new PIXI.Graphics();
16 | rect.beginFill(0xff0000);
17 | rect.drawRect(0,0,100,100);
18 | rect.endFill();
19 | return rect;
20 | }
21 | },
22 | patchProp(el,key,val){
23 | el[key] = val;
24 | },
25 | insert(el,parent){
26 | parent.addChild(el);
27 | }
28 | })
29 | renderer.createApp(App).mount(game.stage);
--------------------------------------------------------------------------------
/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 | render(){
6 | window.self = this;
7 | return h("div",{
8 | name:'App',
9 | id:'root',
10 | class:['red','hard'],
11 | onClick(){
12 | console.log('click')
13 | },
14 | onMousedown(){
15 | console.log('onMousedown')
16 | }
17 | },
18 | [h("div",{},"hi,"+this.msg),h(Foo,{count:1})]
19 | //[h("p",{class:'red'},"hi"),h("h1",{class:"green"},'标题')]
20 | )
21 | },
22 | setup(){
23 | return {
24 | msg:'mini-vue'
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/example/helloworld/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js"
2 |
3 | export const Foo = {
4 | setup(props){
5 | console.log(props)
6 | props.count++;
7 | },
8 | render(){
9 | return h("div",{},"foo "+this.count)
10 | }
11 | }
--------------------------------------------------------------------------------
/example/helloworld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/helloworld/main.js:
--------------------------------------------------------------------------------
1 | import {createApp} from '../../lib/guide-mini-vue.esm.js'
2 | import {App} from './App.js'
3 | const rootContainer = document.querySelector('#app');
4 | createApp(App).mount(rootContainer)
--------------------------------------------------------------------------------
/example/nextTick/App.js:
--------------------------------------------------------------------------------
1 | import {h,ref,getCurrentInstance,nextTick} from '../../lib/guide-mini-vue.esm.js'
2 | export const App = {
3 | name:'App',
4 | setup(){
5 | const count = ref(1);
6 | const instance = getCurrentInstance();
7 | function onClick (){
8 | for(let i = 0;i<100;i++){
9 | console.log('update')
10 | count.value = i;
11 | }
12 | console.log(instance)
13 | nextTick(()=>{
14 | console.log(instance)
15 | })
16 | }
17 |
18 | return {
19 | count,
20 | onClick
21 | }
22 | },
23 | render(){
24 | const button = h("button",{onClick:this.onClick},"update")
25 | const p = h("p",{},"count:"+this.count);
26 | return h("div",{},[button,p])
27 | }
28 | }
--------------------------------------------------------------------------------
/example/nextTick/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/nextTick/main.js:
--------------------------------------------------------------------------------
1 | import {createApp} from '../../lib/guide-mini-vue.esm.js'
2 | import {App} from './App.js'
3 | const rootContainer = document.querySelector('#app');
4 | createApp(App).mount(rootContainer)
--------------------------------------------------------------------------------
/example/patchChildren/App.js:
--------------------------------------------------------------------------------
1 | import {h} from '../../lib/guide-mini-vue.esm.js'
2 | import ArrayToText from './ArrayToText.js'
3 | import TextToText from './TextToText.js';
4 | import TextToArray from './TextToArray.js';
5 | import ArrayToArray from './ArrayToArray.js';
6 | export const App = {
7 | name:'App',
8 | setup(){},
9 | render(){
10 | return h(
11 | "div",
12 | {tId:1},
13 | [
14 | h("p",{},'主页'),
15 | //h(ArrayToText)
16 | //h(TextToText)
17 | //h(TextToArray)
18 | h(ArrayToArray)
19 | ]
20 | )
21 | }
22 | }
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToArray.js:
--------------------------------------------------------------------------------
1 | import {ref,h} from '../../lib/guide-mini-vue.esm.js'
2 | //1、左侧的对比
3 | /* const prevChildren = [
4 | h("p",{key:'A'},'A'),
5 | h("p",{key:'B'},'B'),
6 | h("p",{key:'C'},'C'),
7 | ]
8 | const nextChildren = [
9 | h("p",{key:'A'},'A'),
10 | h("p",{key:'B'},'B'),
11 | h("p",{key:'D'},'D'),
12 | h("p",{key:'E'},'E'),
13 | ] */
14 | //2、右侧的对比
15 | /* const prevChildren = [
16 | h("p",{key:'A'},'A'),
17 | h("p",{key:'B'},'B'),
18 | h("p",{key:'C'},'C'),
19 | ]
20 | const nextChildren = [
21 | h("p",{key:'D'},'D'),
22 | h("p",{key:'E'},'E'),
23 | h("p",{key:'B'},'B'),
24 | h("p",{key:'C'},'C'),
25 | ] */
26 | //3、新的比老的长 左侧相同
27 | /* const prevChildren = [
28 | h("p",{key:'A'},'A'),
29 | h("p",{key:'B'},'B'),
30 | ]
31 | const nextChildren = [
32 | h("p",{key:'A'},'A'),
33 | h("p",{key:'B'},'B'),
34 | h("p",{key:'C'},'C'),
35 | ] */
36 | //4、新的比老的长 右侧相同
37 | /* const prevChildren = [
38 | h("p",{key:'A'},'A'),
39 | h("p",{key:'B'},'B'),
40 | ]
41 | const nextChildren = [
42 | h("p",{key:'D'},'D'),
43 | h("p",{key:'C'},'C'),
44 | h("p",{key:'A'},'A'),
45 | h("p",{key:'B'},'B'),
46 | ] */
47 | //5、新的比老的短 删除 左侧相同
48 | /* const prevChildren = [
49 | h("p",{key:'A'},'A'),
50 | h("p",{key:'B'},'B'),
51 | h("p",{key:'C'},'C'),
52 | ]
53 | const nextChildren = [
54 | h("p",{key:'A'},'A'),
55 | h("p",{key:'B'},'B'),
56 | ]
57 | //6、新的比老的短 删除 右侧相同
58 | /* const prevChildren = [
59 | h("p",{key:'A'},'A'),
60 | h("p",{key:'B'},'B'),
61 | h("p",{key:'C'},'C'),
62 | ]
63 | const nextChildren = [
64 | h("p",{key:'B'},'B'),
65 | h("p",{key:'C'},'C'),
66 | ] */
67 | //7、对比中间的部分
68 | /* const prevChildren = [
69 | h("p",{key:'A'},'A'),
70 | h("p",{key:'B'},'B'),
71 | h("p",{key:'C',id:'prev-c'},'C'),
72 | h("p",{key:'D'},'D'),
73 | h("p",{key:'F'},'F'),
74 | h("p",{key:'G'},'G'),
75 | ]
76 | const nextChildren = [
77 | h("p",{key:'A'},'A'),
78 | h("p",{key:'B'},'B'),
79 | h("p",{key:'E'},'E'),
80 | h("p",{key:'C',id:'next-c'},'C'),
81 | h("p",{key:'F'},'F'),
82 | h("p",{key:'G'},'G'),
83 | ] */
84 | //8、老的比新的长
85 | /* const prevChildren = [
86 | h("p",{key:'A'},'A'),
87 | h("p",{key:'B'},'B'),
88 | h("p",{key:'C',id:'prev-c'},'C'),
89 | h("p",{key:'E'},'E'),
90 | h("p",{key:'D'},'D'),
91 | h("p",{key:'F'},'F'),
92 | h("p",{key:'G'},'G'),
93 | ]
94 | const nextChildren = [
95 | h("p",{key:'A'},'A'),
96 | h("p",{key:'B'},'B'),
97 | h("p",{key:'E'},'E'),
98 | h("p",{key:'C',id:'next-c'},'C'),
99 | h("p",{key:'F'},'F'),
100 | h("p",{key:'G'},'G'),
101 | ] */
102 | //9、最长递增子序列
103 | /* const prevChildren = [
104 | h("p",{key:'A'},'A'),
105 | h("p",{key:'B'},'B'),
106 | h("p",{key:'C'},'C'),
107 | h("p",{key:'D'},'D'),
108 | h("p",{key:'E'},'E'),
109 | h("p",{key:'F'},'F'),
110 | h("p",{key:'G'},'G'),
111 | ]
112 | const nextChildren = [
113 | h("p",{key:'A'},'A'),
114 | h("p",{key:'B'},'B'),
115 | h("p",{key:'E'},'E'),
116 | h("p",{key:'C'},'C'),
117 | h("p",{key:'D'},'D'),
118 | h("p",{key:'F'},'F'),
119 | h("p",{key:'G'},'G'),
120 | ] */
121 | /* const prevChildren = [
122 | h("p",{key:'A'},'A'),
123 | h("p",{key:'B'},'B'),
124 | h("p",{key:'C'},'C'),
125 | h("p",{key:'E'},'E'),
126 | h("p",{key:'F'},'F'),
127 | h("p",{key:'G'},'G'),
128 | ]
129 | const nextChildren = [
130 | h("p",{key:'A'},'A'),
131 | h("p",{key:'B'},'B'),
132 | h("p",{key:'E'},'E'),
133 | h("p",{key:'C'},'C'),
134 | h("p",{key:'D'},'D'),
135 | h("p",{key:'F'},'F'),
136 | h("p",{key:'G'},'G'),
137 | ] */
138 | /* const prevChildren = [
139 | h("p",{key:'A'},'A'),
140 | h("p",{key:'B'},'B'),
141 | h("p",{key:'C'},'C'),
142 | h("p",{key:'D'},'D'),
143 | h("p",{key:'E'},'E'),
144 | h("p",{key:'Z'},'Z'),
145 | h("p",{key:'F'},'F'),
146 | h("p",{key:'G'},'G'),
147 | ]
148 | const nextChildren = [
149 | h("p",{key:'A'},'A'),
150 | h("p",{key:'B'},'B'),
151 | h("p",{key:'D'},'D'),
152 | h("p",{key:'C'},'C'),
153 | h("p",{key:'Y'},'Y'),
154 | h("p",{key:'E'},'E'),
155 | h("p",{key:'F'},'F'),
156 | h("p",{key:'G'},'G'),
157 | ] */
158 | //fix
159 | const prevChildren = [
160 | h("p",{key:'A'},'A'),
161 | h("p",{key:'B'},'B'),
162 | h("p",{},'C'),
163 | h("p",{key:'D'},'D'),
164 | ]
165 | const nextChildren = [
166 | h("p",{key:'A'},'A'),
167 | h("p",{},'C'),
168 | h("p",{key:'B'},'B'),
169 | h("p",{key:'D'},'D'),
170 | ]
171 | export default {
172 | name: 'ArrayToArray',
173 | setup() {
174 | const isChange = ref(false);
175 | window.isChange = isChange;
176 | return {
177 | isChange
178 | }
179 | },
180 | render() {
181 | const self = this;
182 | return self.isChange === true ?
183 | h("div", {}, nextChildren) :
184 | h("div", {}, prevChildren)
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToText.js:
--------------------------------------------------------------------------------
1 | import {h,ref} from '../../lib/guide-mini-vue.esm.js';
2 | const nextChildren = 'newChildren';
3 | const prevChildren = [h("div",{},"A"),h("div",{},"B")];
4 | export default {
5 | name:'ArrayToText',
6 | setup(){
7 | const isChange = ref(false);
8 | window.isChange = isChange;
9 | return {
10 | isChange
11 | }
12 | },
13 | render(){
14 | const self = this;
15 | return self.isChange === true
16 | ? h("div",{},nextChildren)
17 | : h("div",{},prevChildren)
18 | }
19 | }
--------------------------------------------------------------------------------
/example/patchChildren/TextToArray.js:
--------------------------------------------------------------------------------
1 | import {h,ref} from '../../lib/guide-mini-vue.esm.js';
2 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")];
3 | const prevChildren = 'newChildren';
4 | export default {
5 | name:'ArrayToText',
6 | setup(){
7 | const isChange = ref(false);
8 | window.isChange = isChange;
9 | return {
10 | isChange
11 | }
12 | },
13 | render(){
14 | const self = this;
15 | return self.isChange === true
16 | ? h("div",{},nextChildren)
17 | : h("div",{},prevChildren)
18 | }
19 | }
--------------------------------------------------------------------------------
/example/patchChildren/TextToText.js:
--------------------------------------------------------------------------------
1 | import {h,ref} from '../../lib/guide-mini-vue.esm.js';
2 | const nextChildren = 'newChildren';
3 | const prevChildren = 'oldChildren';
4 | export default {
5 | name:'ArrayToText',
6 | setup(){
7 | const isChange = ref(false);
8 | window.isChange = isChange;
9 | return {
10 | isChange
11 | }
12 | },
13 | render(){
14 | const self = this;
15 | return self.isChange === true
16 | ? h("div",{},nextChildren)
17 | : h("div",{},prevChildren)
18 | }
19 | }
--------------------------------------------------------------------------------
/example/patchChildren/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/patchChildren/main.js:
--------------------------------------------------------------------------------
1 | import {createApp} from '../../lib/guide-mini-vue.esm.js'
2 | import {App} from './App.js'
3 | const rootContainer = document.querySelector('#app');
4 | createApp(App).mount(rootContainer)
--------------------------------------------------------------------------------
/example/update/App.js:
--------------------------------------------------------------------------------
1 | import {h,ref} from '../../lib/guide-mini-vue.esm.js'
2 | export const App = {
3 | name:'App',
4 | setup(){
5 | const count = ref(0);
6 | const onClick = ()=>{
7 | count.value++;
8 | }
9 | const props = ref({
10 | foo:'foo',
11 | bar:'bar'
12 | })
13 | const onChangePropsDemo1 = ()=>{
14 | props.value.foo = 'new-foo';
15 | }
16 | const onChangePropsDemo2 = ()=>{
17 | props.value.foo = undefined;
18 | }
19 | const onChangePropsDemo3 = ()=>{
20 | props.value = {
21 | foo:'foo'
22 | }
23 | }
24 | return {
25 | count,
26 | props,
27 | onClick,
28 | onChangePropsDemo1,
29 | onChangePropsDemo2,
30 | onChangePropsDemo3
31 | }
32 | },
33 | render(){
34 | return h(
35 | "div",
36 | {
37 | id:"root",
38 | ...this.props
39 | },
40 | [
41 | h("div",{},"count:"+this.count),//依赖收集
42 | h(
43 | "button",
44 | {
45 | onClick:this.onClick
46 | },
47 | "click"
48 | ),
49 | h(
50 | "button",
51 | {
52 | onClick:this.onChangePropsDemo1
53 | },
54 | "changeProps - 值改变了 - 修改"
55 | ),
56 | h(
57 | "button",
58 | {
59 | onClick:this.onChangePropsDemo2
60 | },
61 | "changeProps - 值改变了 undefined - 删除"
62 | ),
63 | h(
64 | "button", {
65 | onClick: this.onChangePropsDemo3
66 | },
67 | "changeProps - key在新的里面没有了 - 删除"
68 | )
69 | ]
70 | )
71 | }
72 | }
--------------------------------------------------------------------------------
/example/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/update/main.js:
--------------------------------------------------------------------------------
1 | import {createApp} from '../../lib/guide-mini-vue.esm.js'
2 | import {App} from './App.js'
3 | const rootContainer = document.querySelector('#app');
4 | createApp(App).mount(rootContainer)
--------------------------------------------------------------------------------
/lib/guide-mini-vue.cjs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | const Fragment = Symbol("Fragment");
6 | const Text = Symbol("Text");
7 | function createVnode(type, props, children) {
8 | const vnode = {
9 | type,
10 | props,
11 | children,
12 | el: null,
13 | key: props && props.key,
14 | component: null,
15 | shapeFlag: getShapeFlag(type)
16 | };
17 | if (typeof children === 'string') {
18 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */;
19 | }
20 | else if (Array.isArray(children)) {
21 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
22 | }
23 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
24 | if (typeof children === 'object') {
25 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */;
26 | }
27 | }
28 | return vnode;
29 | }
30 | function createTextVnode(text) {
31 | return createVnode(Text, {}, text);
32 | }
33 | function getShapeFlag(type) {
34 | return typeof type === "string" ? 1 /* ELEMENT */ : 2 /* STATEFUL_COMPONENT */;
35 | }
36 |
37 | function h(type, props, children) {
38 | return createVnode(type, props, children);
39 | }
40 |
41 | function renderSlots(slots, name, props) {
42 | const slot = slots[name];
43 | if (slot) {
44 | if (typeof slot === 'function') {
45 | return createVnode(Fragment, {}, slot(props));
46 | }
47 | }
48 | }
49 |
50 | const extend = Object.assign;
51 | const isObject = (val) => {
52 | return val !== null && typeof val === "object";
53 | };
54 | const hasChanged = (newValue, value) => {
55 | return !Object.is(newValue, value);
56 | };
57 | const camelize = (str) => {
58 | return str.replace(/-(\w)/g, (_, c) => {
59 | return c ? c.toUpperCase() : "";
60 | });
61 | };
62 | const capitalize = (str) => {
63 | return str.charAt(0).toUpperCase() + str.slice(1);
64 | };
65 | const toHandlerKey = (str) => {
66 | return str ? "on" + capitalize(str) : "";
67 | };
68 |
69 | let activeEffect;
70 | let shouldTrack;
71 | class ReactiveEffect {
72 | constructor(fn, scheduler) {
73 | this.scheduler = scheduler;
74 | this.deps = [];
75 | this.active = true;
76 | this._fn = fn;
77 | }
78 | run() {
79 | if (!this.active) {
80 | return this._fn();
81 | }
82 | shouldTrack = true;
83 | activeEffect = this;
84 | const result = this._fn();
85 | shouldTrack = false;
86 | return result;
87 | }
88 | stop() {
89 | if (this.active) {
90 | cleanupEffect(this);
91 | if (this.onStop)
92 | this.onStop();
93 | this.active = false;
94 | }
95 | }
96 | }
97 | function cleanupEffect(effect) {
98 | effect.deps.forEach((dep) => {
99 | dep.delete(effect);
100 | });
101 | effect.deps.length = 0;
102 | }
103 | let targetMap = new Map();
104 | function track(target, key) {
105 | //targetMap target key
106 | if (!isTracking())
107 | return;
108 | let depsMap = targetMap.get(target);
109 | if (!depsMap) {
110 | depsMap = new Map();
111 | targetMap.set(target, depsMap);
112 | }
113 | let deps = depsMap.get(key);
114 | if (!deps) {
115 | deps = new Set();
116 | depsMap.set(key, deps);
117 | }
118 | trackEffects(deps);
119 | }
120 | function trackEffects(deps) {
121 | if (deps.has(activeEffect))
122 | return;
123 | deps.add(activeEffect);
124 | //反向收集
125 | activeEffect.deps.push(deps);
126 | }
127 | function isTracking() {
128 | return shouldTrack && activeEffect !== undefined;
129 | }
130 | function trigger(target, key) {
131 | let depsMap = targetMap.get(target);
132 | if (!depsMap)
133 | return;
134 | let deps = depsMap.get(key);
135 | triggerEffect(deps);
136 | }
137 | function triggerEffect(deps) {
138 | deps && deps.forEach((effect) => {
139 | if (effect.scheduler) {
140 | effect.scheduler();
141 | }
142 | else {
143 | effect.run();
144 | }
145 | });
146 | }
147 | function effect(fn, options = {}) {
148 | const _effect = new ReactiveEffect(fn, options.scheduler);
149 | extend(_effect, options);
150 | _effect.onStop = options.onStop;
151 | _effect.run();
152 | const runner = _effect.run.bind(_effect);
153 | runner.effect = _effect;
154 | return runner;
155 | }
156 |
157 | const get = createGetter();
158 | const set = createSetter();
159 | const readonlyGet = createGetter(true);
160 | const shallowReadonlyGet = createGetter(true, true);
161 | function createGetter(isReadonly = false, shallow = false) {
162 | return function get(target, key) {
163 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
164 | return !isReadonly;
165 | }
166 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
167 | return isReadonly;
168 | }
169 | const res = Reflect.get(target, key);
170 | if (shallow)
171 | return res;
172 | if (isObject(res)) {
173 | return isReadonly ? readonly(res) : reactive(res);
174 | }
175 | if (!isReadonly) {
176 | track(target, key);
177 | }
178 | return res;
179 | };
180 | }
181 | function createSetter() {
182 | return function set(target, key, value) {
183 | const res = Reflect.set(target, key, value);
184 | trigger(target, key);
185 | return res;
186 | };
187 | }
188 | const mutableHandlers = {
189 | get,
190 | set,
191 | };
192 | const readonlyHandlers = {
193 | get: readonlyGet,
194 | set(target, key, value) {
195 | console.warn(`key:${key} set失败了,因为target是readonly的`, target);
196 | return true;
197 | },
198 | };
199 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
200 | get: shallowReadonlyGet
201 | });
202 |
203 | function reactive(raw) {
204 | return createActiveObject(raw, mutableHandlers);
205 | }
206 | function readonly(raw) {
207 | return createActiveObject(raw, readonlyHandlers);
208 | }
209 | function shallowReadonly(raw) {
210 | return createActiveObject(raw, shallowReadonlyHandlers);
211 | }
212 | function createActiveObject(target, baseHandlers) {
213 | if (!isObject(target)) {
214 | console.warn(`target ${target} 必须是一个对象`);
215 | return target;
216 | }
217 | return new Proxy(target, baseHandlers);
218 | }
219 |
220 | class RefImpl {
221 | constructor(value) {
222 | this._v_ref = true;
223 | this._rawValue = value;
224 | this._value = convert(value);
225 | this.dep = new Set();
226 | }
227 | get value() {
228 | trackRefEffects(this);
229 | return this._value;
230 | }
231 | set value(newValue) {
232 | if (hasChanged(newValue, this._rawValue)) {
233 | this._rawValue = newValue;
234 | this._value = convert(newValue);
235 | triggerEffect(this.dep);
236 | }
237 | }
238 | }
239 | function convert(value) {
240 | return isObject(value) ? reactive(value) : value;
241 | }
242 | function trackRefEffects(ref) {
243 | if (isTracking()) {
244 | trackEffects(ref.dep);
245 | }
246 | }
247 | function ref(value) {
248 | return new RefImpl(value);
249 | }
250 | function isRef(value) {
251 | return !!value._v_ref;
252 | }
253 | function unRef(ref) {
254 | return isRef(ref) ? ref.value : ref;
255 | }
256 | function proxyRefs(objectWithRefs) {
257 | return new Proxy(objectWithRefs, {
258 | get(target, key) {
259 | return unRef(Reflect.get(target, key));
260 | },
261 | set(target, key, value) {
262 | if (isRef(target[key]) && !isRef(value)) {
263 | return (target[key].value = value);
264 | }
265 | else {
266 | return Reflect.set(target, key, value);
267 | }
268 | }
269 | });
270 | }
271 |
272 | function emit(instance, event, ...args) {
273 | const { props } = instance;
274 | const HandlerName = toHandlerKey(camelize(event));
275 | const handler = props[HandlerName];
276 | handler && handler(...args);
277 | }
278 |
279 | function initProps(instance, rawProps = {}) {
280 | instance.props = rawProps;
281 | }
282 |
283 | const publicPropertiesMap = {
284 | $el: (i) => i.vnode.el,
285 | $slots: (i) => i.slots,
286 | $props: (i) => i.props
287 | };
288 | const PublicInstanceProxyHandlers = {
289 | get({ _: instance }, key) {
290 | const { setupState, props } = instance;
291 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
292 | if (hasOwn(setupState, key)) {
293 | return setupState[key];
294 | }
295 | else if (hasOwn(props, key)) {
296 | return props[key];
297 | }
298 | const publicGetter = publicPropertiesMap[key];
299 | if (publicGetter) {
300 | return publicGetter(instance);
301 | }
302 | },
303 | };
304 |
305 | function initSlots(instance, children) {
306 | const { vnode } = instance;
307 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
308 | normalizeObjectSlots(children, instance.slots);
309 | }
310 | }
311 | function normalizeSlotValue(value) {
312 | return Array.isArray(value) ? value : [value];
313 | }
314 | function normalizeObjectSlots(children, slots) {
315 | for (const key in children) {
316 | const value = children[key];
317 | if (typeof value === "function") {
318 | slots[key] = (props) => normalizeSlotValue(value(props));
319 | }
320 | }
321 | }
322 |
323 | function createComponentInstance(vnode, parent) {
324 | const component = {
325 | vnode,
326 | type: vnode.type,
327 | setupState: {},
328 | props: {},
329 | slots: {},
330 | provides: parent ? parent.provides : {},
331 | parent,
332 | isMounted: false,
333 | subTree: {},
334 | next: null,
335 | emit: () => { },
336 | };
337 | component.emit = emit.bind(null, component);
338 | return component;
339 | }
340 | function setupComponent(instance) {
341 | initProps(instance, instance.vnode.props);
342 | initSlots(instance, instance.vnode.children);
343 | setupStatefulComponent(instance);
344 | }
345 | function setupStatefulComponent(instance) {
346 | const Component = instance.type;
347 | instance.proxy = new Proxy({
348 | _: instance,
349 | }, PublicInstanceProxyHandlers);
350 | const { setup } = Component;
351 | if (setup) {
352 | setCurrentInstance(instance);
353 | const setupResult = setup(shallowReadonly(instance.props), {
354 | emit: instance.emit
355 | });
356 | handleSetupResult(instance, setupResult);
357 | setCurrentInstance(null);
358 | }
359 | }
360 | function handleSetupResult(instance, setupResult) {
361 | if (typeof setupResult === "object") {
362 | instance.setupState = proxyRefs(setupResult);
363 | }
364 | finishComponentSetup(instance);
365 | }
366 | function finishComponentSetup(instance) {
367 | const Component = instance.type;
368 | instance.render = Component.render;
369 | }
370 | let currentInstance = null;
371 | function getCurrentInstance() {
372 | return currentInstance;
373 | }
374 | function setCurrentInstance(instance) {
375 | currentInstance = instance;
376 | }
377 |
378 | function provide(key, value) {
379 | const currentInstance = getCurrentInstance();
380 | if (currentInstance) {
381 | let { provides } = currentInstance;
382 | const parentProvides = currentInstance.parent.provides;
383 | if (provides === parentProvides) {
384 | provides = currentInstance.provides = Object.create(parentProvides);
385 | }
386 | provides[key] = value;
387 | }
388 | }
389 | function inject(key, defaultValue) {
390 | const currentInstance = getCurrentInstance();
391 | if (currentInstance) {
392 | const parentProvides = currentInstance.parent.provides;
393 | if (parentProvides[key]) {
394 | return parentProvides[key];
395 | }
396 | else if (defaultValue) {
397 | if (typeof defaultValue === 'function') {
398 | return defaultValue();
399 | }
400 | return defaultValue;
401 | }
402 | }
403 | }
404 |
405 | function shouldUpdateComponent(prevVnode, nextVnode) {
406 | const { props: prevProps } = prevVnode;
407 | const { props: nextProps } = nextVnode;
408 | for (const key in nextProps) {
409 | if (nextProps[key] !== prevProps[key]) {
410 | return true;
411 | }
412 | }
413 | return false;
414 | }
415 |
416 | function createAppAPI(render) {
417 | return function createApp(rootComponent) {
418 | return {
419 | mount(rootContainer) {
420 | const vnode = createVnode(rootComponent);
421 | render(vnode, rootContainer);
422 | },
423 | };
424 | };
425 | }
426 |
427 | const queue = [];
428 | let isFlushPending = false;
429 | const p = Promise.resolve();
430 | function nextTick(fn) {
431 | return fn ? p.then(fn) : p;
432 | }
433 | function queueJobs(job) {
434 | if (!queue.includes(job)) {
435 | queue.push(job);
436 | }
437 | queueFlush();
438 | }
439 | function queueFlush() {
440 | if (isFlushPending)
441 | return;
442 | isFlushPending = true;
443 | nextTick(flushJobs);
444 | }
445 | function flushJobs() {
446 | isFlushPending = false;
447 | let job;
448 | while ((job = queue.shift())) {
449 | job && job();
450 | }
451 | }
452 |
453 | function createRenderer(options) {
454 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options;
455 | function render(vnode, container, parentComponent) {
456 | patch(null, vnode, container, parentComponent, null);
457 | }
458 | //n1 old n2 new
459 | function patch(n1, n2, container, parentComponent, anchor) {
460 | const { type, shapeFlag } = n2;
461 | switch (type) {
462 | case Fragment:
463 | processFragment(n1, n2, container, parentComponent, anchor);
464 | break;
465 | case Text:
466 | processText(n1, n2, container);
467 | break;
468 | default:
469 | if (shapeFlag & 1 /* ELEMENT */) {
470 | processElement(n1, n2, container, parentComponent, anchor);
471 | }
472 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
473 | processComponent(n1, n2, container, parentComponent, anchor);
474 | }
475 | break;
476 | }
477 | }
478 | function processElement(n1, n2, container, parentComponent, anchor) {
479 | if (!n1) {
480 | mountElement(n1, n2, container, parentComponent, anchor);
481 | }
482 | else {
483 | patchElement(n1, n2, container, parentComponent, anchor);
484 | }
485 | }
486 | function patchElement(n1, n2, container, parentComponent, anchor) {
487 | console.log("patchComponent");
488 | console.log("n1", n1);
489 | console.log("n2", n2);
490 | const oldProps = n1.props || EMPTY_OBJ;
491 | const newProps = n2.props || EMPTY_OBJ;
492 | const el = (n2.el = n1.el);
493 | patchChildren(n1, n2, el, parentComponent, anchor);
494 | patchProps(el, oldProps, newProps);
495 | }
496 | function patchChildren(n1, n2, container, parentComponent, anchor) {
497 | const prevShapeFlag = n1.shapeFlag;
498 | const { shapeFlag } = n2;
499 | const c1 = n1.children;
500 | const c2 = n2.children;
501 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
502 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) {
503 | //1 把n1都清空
504 | unmountChildren(n1.children);
505 | }
506 | //2 设置text
507 | if (c1 !== c2) {
508 | hostSetElementText(container, c2);
509 | }
510 | }
511 | else {
512 | //text to array
513 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) {
514 | hostSetElementText(container, "");
515 | mountChildren(c2, container, parentComponent, anchor);
516 | }
517 | else {
518 | //array
519 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
520 | }
521 | }
522 | }
523 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) {
524 | let i = 0;
525 | const l2 = c2.length;
526 | let e1 = c1.length - 1;
527 | let e2 = l2 - 1;
528 | function isSomeVNodeType(n1, n2) {
529 | return n1.type === n2.type && n1.key === n2.key;
530 | }
531 | //左侧
532 | while (i <= e1 && i <= e2) {
533 | const n1 = c1[i];
534 | const n2 = c2[i];
535 | if (isSomeVNodeType(n1, n2)) {
536 | patch(n1, n2, container, parentComponent, parentAnchor);
537 | }
538 | else {
539 | break;
540 | }
541 | i++;
542 | }
543 | //右侧
544 | while (i <= e1 && i <= e2) {
545 | const n1 = c1[e1];
546 | const n2 = c2[e2];
547 | if (isSomeVNodeType(n1, n2)) {
548 | patch(n1, n2, container, parentComponent, parentAnchor);
549 | }
550 | else {
551 | break;
552 | }
553 | e1--;
554 | e2--;
555 | }
556 | //新的比老的多 创建
557 | if (i > e1) {
558 | if (i <= e2) {
559 | const nextPos = e2 + 1;
560 | const anchor = e2 + 1 < l2 ? c2[nextPos].el : null;
561 | while (i <= e2) {
562 | patch(null, c2[i], container, parentComponent, anchor);
563 | i++;
564 | }
565 | }
566 | }
567 | else if (i > e2) {
568 | while (i <= e1) {
569 | hostRemove(c1[i].el);
570 | i++;
571 | }
572 | }
573 | else {
574 | let s1 = i;
575 | let s2 = i;
576 | const toBePatched = e2 - s2 + 1;
577 | let patched = 0;
578 | const keyToNewIndexMap = new Map();
579 | const newIndexToOldIndexMap = new Array(toBePatched);
580 | let moved = false;
581 | let maxNewIndexSoFar = 0;
582 | for (let i = 0; i < toBePatched; i++)
583 | newIndexToOldIndexMap[i] = 0;
584 | for (let i = s2; i <= e2; i++) {
585 | const nextChild = c2[i];
586 | keyToNewIndexMap.set(nextChild.key, i);
587 | }
588 | for (let i = s1; i <= e1; i++) {
589 | const prevChild = c1[i];
590 | if (patched >= toBePatched) {
591 | hostRemove(prevChild.el);
592 | continue;
593 | }
594 | let newIndex;
595 | if (prevChild.key != null) {
596 | newIndex = keyToNewIndexMap.get(prevChild.key);
597 | }
598 | else {
599 | for (let j = s2; j <= e2; j++) {
600 | if (isSomeVNodeType(prevChild, c2[j])) {
601 | newIndex = j;
602 | break;
603 | }
604 | }
605 | }
606 | if (newIndex === undefined) {
607 | hostRemove(prevChild.el);
608 | }
609 | else {
610 | if (newIndex >= maxNewIndexSoFar) {
611 | maxNewIndexSoFar = newIndex;
612 | }
613 | else {
614 | moved = true;
615 | }
616 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
617 | patch(prevChild, c2[newIndex], container, parentComponent, null);
618 | patched++;
619 | }
620 | }
621 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];
622 | let j = increasingNewIndexSequence.length - 1;
623 | for (let i = toBePatched - 1; i >= 0; i--) {
624 | const nextIndex = i + s2;
625 | const nextChild = c2[nextIndex];
626 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
627 | if (newIndexToOldIndexMap[i] === 0) {
628 | patch(null, nextChild, container, parentComponent, anchor);
629 | }
630 | else if (moved) {
631 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
632 | console.log("移动位置");
633 | hostInsert(nextChild.el, container, anchor);
634 | }
635 | else {
636 | j--;
637 | }
638 | }
639 | }
640 | }
641 | }
642 | function getSequence(arr) {
643 | const p = arr.slice();
644 | const result = [0];
645 | let i, j, u, v, c;
646 | const len = arr.length;
647 | for (i = 0; i < len; i++) {
648 | const arrI = arr[i];
649 | if (arrI !== 0) {
650 | j = result[result.length - 1];
651 | if (arr[j] < arrI) {
652 | p[i] = j;
653 | result.push(i);
654 | continue;
655 | }
656 | u = 0;
657 | v = result.length - 1;
658 | while (u < v) {
659 | c = (u + v) >> 1;
660 | if (arr[result[c]] < arrI) {
661 | u = c + 1;
662 | }
663 | else {
664 | v = c;
665 | }
666 | }
667 | if (arrI < arr[result[u]]) {
668 | if (u > 0) {
669 | p[i] = result[u - 1];
670 | }
671 | result[u] = i;
672 | }
673 | }
674 | }
675 | u = result.length;
676 | v = result[u - 1];
677 | while (u-- > 0) {
678 | result[u] = v;
679 | v = p[v];
680 | }
681 | return result;
682 | }
683 | function unmountChildren(children) {
684 | for (let i = 0; i < children.length; i++) {
685 | const el = children[i].el;
686 | hostRemove(el);
687 | }
688 | }
689 | const EMPTY_OBJ = {};
690 | function patchProps(el, oldProps, newProps) {
691 | if (oldProps !== newProps) {
692 | for (let key in newProps) {
693 | const prevProp = oldProps[key];
694 | const nextProp = newProps[key];
695 | if (prevProp !== nextProp) {
696 | hostPatchProp(el, key, prevProp, nextProp);
697 | }
698 | }
699 | if (oldProps !== EMPTY_OBJ)
700 | for (let key in oldProps) {
701 | if (!(key in newProps)) {
702 | hostPatchProp(el, key, oldProps[key], null);
703 | }
704 | }
705 | }
706 | }
707 | function mountElement(n1, n2, container, parentComponent, anchor) {
708 | const el = (n2.el = hostCreateElement(n2.type));
709 | const { children, shapeFlag } = n2;
710 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
711 | el.textContent = children;
712 | }
713 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
714 | mountChildren(n2.children, el, parentComponent, anchor);
715 | }
716 | const { props } = n2;
717 | for (const key in props) {
718 | const val = props[key];
719 | hostPatchProp(el, key, null, val);
720 | }
721 | hostInsert(el, container, anchor);
722 | }
723 | function processFragment(n1, n2, container, parentComponent, anchor) {
724 | mountChildren(n2.children, container, parentComponent, anchor);
725 | }
726 | function processText(n1, n2, container) {
727 | const { children } = n2;
728 | const textNode = (n2.el = document.createTextNode(children));
729 | container.append(textNode);
730 | }
731 | function mountChildren(children, container, parentComponent, anchor) {
732 | children.forEach((v) => {
733 | patch(null, v, container, parentComponent, anchor);
734 | });
735 | }
736 | function processComponent(n1, n2, container, parentComponent, anchor) {
737 | if (!n1) {
738 | mountComponent(n2, container, parentComponent, anchor);
739 | }
740 | else {
741 | updateComponent(n1, n2);
742 | }
743 | }
744 | function updateComponent(n1, n2) {
745 | const instance = (n2.component = n1.component);
746 | if (shouldUpdateComponent(n1, n2)) {
747 | instance.next = n2;
748 | instance.update();
749 | }
750 | else {
751 | n2.component = n1.component;
752 | n2.el = n1.el;
753 | instance.vnode = n2;
754 | }
755 | }
756 | function mountComponent(initialVnode, container, parentComponent, anchor) {
757 | const instance = (initialVnode.component = createComponentInstance(initialVnode, parentComponent));
758 | setupComponent(instance);
759 | setupRenderEffect(instance, initialVnode, container, anchor);
760 | }
761 | function setupRenderEffect(instance, initialVnode, container, anchor) {
762 | instance.update = effect(() => {
763 | if (!instance.isMounted) {
764 | console.log("init");
765 | const { proxy } = instance;
766 | const subTree = (instance.subTree = instance.render.call(proxy));
767 | patch(null, subTree, container, instance, anchor);
768 | initialVnode.el = subTree.el;
769 | instance.isMounted = true;
770 | }
771 | else {
772 | console.log("update");
773 | const { next, vnode } = instance;
774 | if (next) {
775 | next.el = vnode.el;
776 | updateComponentPreRender(instance, next);
777 | }
778 | const { proxy } = instance;
779 | const subTree = instance.render.call(proxy);
780 | const prevSubTree = instance.subTree;
781 | instance.subTree = subTree;
782 | patch(prevSubTree, subTree, container, instance, anchor);
783 | }
784 | }, {
785 | scheduler() {
786 | console.log('update--scheduler');
787 | queueJobs(instance.update);
788 | }
789 | });
790 | }
791 | return {
792 | createApp: createAppAPI(render)
793 | };
794 | }
795 | function updateComponentPreRender(instance, nextVnode) {
796 | instance.vnode = nextVnode;
797 | instance.next = null;
798 | instance.props = nextVnode.props;
799 | }
800 |
801 | function createElement(type) {
802 | return document.createElement(type);
803 | }
804 | function patchProp(el, key, oldVal, newVal) {
805 | const isOn = (key) => /^on[A-Z]/.test(key);
806 | if (isOn(key)) {
807 | const event = key.slice(2).toLowerCase();
808 | el.addEventListener(event, newVal);
809 | }
810 | else {
811 | if (newVal === undefined || newVal === null) {
812 | el.removeAttribute(key, newVal);
813 | }
814 | else {
815 | el.setAttribute(key, newVal);
816 | }
817 | }
818 | }
819 | function insert(child, parent, anchor) {
820 | parent.insertBefore(child, anchor || null);
821 | }
822 | function remove(child) {
823 | const parent = child.parentNode;
824 | if (parent) {
825 | parent.removeChild(child);
826 | }
827 | }
828 | function setElementText(container, text) {
829 | container.textContent = text;
830 | }
831 | const render = createRenderer({
832 | createElement,
833 | patchProp,
834 | insert,
835 | remove,
836 | setElementText
837 | });
838 | function createApp(...args) {
839 | return render.createApp(...args);
840 | }
841 |
842 | exports.createApp = createApp;
843 | exports.createRenderer = createRenderer;
844 | exports.createTextVnode = createTextVnode;
845 | exports.getCurrentInstance = getCurrentInstance;
846 | exports.h = h;
847 | exports.inject = inject;
848 | exports.nextTick = nextTick;
849 | exports.provide = provide;
850 | exports.proxyRefs = proxyRefs;
851 | exports.ref = ref;
852 | exports.renderSlots = renderSlots;
853 |
--------------------------------------------------------------------------------
/lib/guide-mini-vue.esm.js:
--------------------------------------------------------------------------------
1 | const Fragment = Symbol("Fragment");
2 | const Text = Symbol("Text");
3 | function createVnode(type, props, children) {
4 | const vnode = {
5 | type,
6 | props,
7 | children,
8 | el: null,
9 | key: props && props.key,
10 | component: null,
11 | shapeFlag: getShapeFlag(type)
12 | };
13 | if (typeof children === 'string') {
14 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */;
15 | }
16 | else if (Array.isArray(children)) {
17 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
18 | }
19 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
20 | if (typeof children === 'object') {
21 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */;
22 | }
23 | }
24 | return vnode;
25 | }
26 | function createTextVnode(text) {
27 | return createVnode(Text, {}, text);
28 | }
29 | function getShapeFlag(type) {
30 | return typeof type === "string" ? 1 /* ELEMENT */ : 2 /* STATEFUL_COMPONENT */;
31 | }
32 |
33 | function h(type, props, children) {
34 | return createVnode(type, props, children);
35 | }
36 |
37 | function renderSlots(slots, name, props) {
38 | const slot = slots[name];
39 | if (slot) {
40 | if (typeof slot === 'function') {
41 | return createVnode(Fragment, {}, slot(props));
42 | }
43 | }
44 | }
45 |
46 | const extend = Object.assign;
47 | const isObject = (val) => {
48 | return val !== null && typeof val === "object";
49 | };
50 | const hasChanged = (newValue, value) => {
51 | return !Object.is(newValue, value);
52 | };
53 | const camelize = (str) => {
54 | return str.replace(/-(\w)/g, (_, c) => {
55 | return c ? c.toUpperCase() : "";
56 | });
57 | };
58 | const capitalize = (str) => {
59 | return str.charAt(0).toUpperCase() + str.slice(1);
60 | };
61 | const toHandlerKey = (str) => {
62 | return str ? "on" + capitalize(str) : "";
63 | };
64 |
65 | let activeEffect;
66 | let shouldTrack;
67 | class ReactiveEffect {
68 | constructor(fn, scheduler) {
69 | this.scheduler = scheduler;
70 | this.deps = [];
71 | this.active = true;
72 | this._fn = fn;
73 | }
74 | run() {
75 | if (!this.active) {
76 | return this._fn();
77 | }
78 | shouldTrack = true;
79 | activeEffect = this;
80 | const result = this._fn();
81 | shouldTrack = false;
82 | return result;
83 | }
84 | stop() {
85 | if (this.active) {
86 | cleanupEffect(this);
87 | if (this.onStop)
88 | this.onStop();
89 | this.active = false;
90 | }
91 | }
92 | }
93 | function cleanupEffect(effect) {
94 | effect.deps.forEach((dep) => {
95 | dep.delete(effect);
96 | });
97 | effect.deps.length = 0;
98 | }
99 | let targetMap = new Map();
100 | function track(target, key) {
101 | //targetMap target key
102 | if (!isTracking())
103 | return;
104 | let depsMap = targetMap.get(target);
105 | if (!depsMap) {
106 | depsMap = new Map();
107 | targetMap.set(target, depsMap);
108 | }
109 | let deps = depsMap.get(key);
110 | if (!deps) {
111 | deps = new Set();
112 | depsMap.set(key, deps);
113 | }
114 | trackEffects(deps);
115 | }
116 | function trackEffects(deps) {
117 | if (deps.has(activeEffect))
118 | return;
119 | deps.add(activeEffect);
120 | //反向收集
121 | activeEffect.deps.push(deps);
122 | }
123 | function isTracking() {
124 | return shouldTrack && activeEffect !== undefined;
125 | }
126 | function trigger(target, key) {
127 | let depsMap = targetMap.get(target);
128 | if (!depsMap)
129 | return;
130 | let deps = depsMap.get(key);
131 | triggerEffect(deps);
132 | }
133 | function triggerEffect(deps) {
134 | deps && deps.forEach((effect) => {
135 | if (effect.scheduler) {
136 | effect.scheduler();
137 | }
138 | else {
139 | effect.run();
140 | }
141 | });
142 | }
143 | function effect(fn, options = {}) {
144 | const _effect = new ReactiveEffect(fn, options.scheduler);
145 | extend(_effect, options);
146 | _effect.onStop = options.onStop;
147 | _effect.run();
148 | const runner = _effect.run.bind(_effect);
149 | runner.effect = _effect;
150 | return runner;
151 | }
152 |
153 | const get = createGetter();
154 | const set = createSetter();
155 | const readonlyGet = createGetter(true);
156 | const shallowReadonlyGet = createGetter(true, true);
157 | function createGetter(isReadonly = false, shallow = false) {
158 | return function get(target, key) {
159 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
160 | return !isReadonly;
161 | }
162 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
163 | return isReadonly;
164 | }
165 | const res = Reflect.get(target, key);
166 | if (shallow)
167 | return res;
168 | if (isObject(res)) {
169 | return isReadonly ? readonly(res) : reactive(res);
170 | }
171 | if (!isReadonly) {
172 | track(target, key);
173 | }
174 | return res;
175 | };
176 | }
177 | function createSetter() {
178 | return function set(target, key, value) {
179 | const res = Reflect.set(target, key, value);
180 | trigger(target, key);
181 | return res;
182 | };
183 | }
184 | const mutableHandlers = {
185 | get,
186 | set,
187 | };
188 | const readonlyHandlers = {
189 | get: readonlyGet,
190 | set(target, key, value) {
191 | console.warn(`key:${key} set失败了,因为target是readonly的`, target);
192 | return true;
193 | },
194 | };
195 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
196 | get: shallowReadonlyGet
197 | });
198 |
199 | function reactive(raw) {
200 | return createActiveObject(raw, mutableHandlers);
201 | }
202 | function readonly(raw) {
203 | return createActiveObject(raw, readonlyHandlers);
204 | }
205 | function shallowReadonly(raw) {
206 | return createActiveObject(raw, shallowReadonlyHandlers);
207 | }
208 | function createActiveObject(target, baseHandlers) {
209 | if (!isObject(target)) {
210 | console.warn(`target ${target} 必须是一个对象`);
211 | return target;
212 | }
213 | return new Proxy(target, baseHandlers);
214 | }
215 |
216 | class RefImpl {
217 | constructor(value) {
218 | this._v_ref = true;
219 | this._rawValue = value;
220 | this._value = convert(value);
221 | this.dep = new Set();
222 | }
223 | get value() {
224 | trackRefEffects(this);
225 | return this._value;
226 | }
227 | set value(newValue) {
228 | if (hasChanged(newValue, this._rawValue)) {
229 | this._rawValue = newValue;
230 | this._value = convert(newValue);
231 | triggerEffect(this.dep);
232 | }
233 | }
234 | }
235 | function convert(value) {
236 | return isObject(value) ? reactive(value) : value;
237 | }
238 | function trackRefEffects(ref) {
239 | if (isTracking()) {
240 | trackEffects(ref.dep);
241 | }
242 | }
243 | function ref(value) {
244 | return new RefImpl(value);
245 | }
246 | function isRef(value) {
247 | return !!value._v_ref;
248 | }
249 | function unRef(ref) {
250 | return isRef(ref) ? ref.value : ref;
251 | }
252 | function proxyRefs(objectWithRefs) {
253 | return new Proxy(objectWithRefs, {
254 | get(target, key) {
255 | return unRef(Reflect.get(target, key));
256 | },
257 | set(target, key, value) {
258 | if (isRef(target[key]) && !isRef(value)) {
259 | return (target[key].value = value);
260 | }
261 | else {
262 | return Reflect.set(target, key, value);
263 | }
264 | }
265 | });
266 | }
267 |
268 | function emit(instance, event, ...args) {
269 | const { props } = instance;
270 | const HandlerName = toHandlerKey(camelize(event));
271 | const handler = props[HandlerName];
272 | handler && handler(...args);
273 | }
274 |
275 | function initProps(instance, rawProps = {}) {
276 | instance.props = rawProps;
277 | }
278 |
279 | const publicPropertiesMap = {
280 | $el: (i) => i.vnode.el,
281 | $slots: (i) => i.slots,
282 | $props: (i) => i.props
283 | };
284 | const PublicInstanceProxyHandlers = {
285 | get({ _: instance }, key) {
286 | const { setupState, props } = instance;
287 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
288 | if (hasOwn(setupState, key)) {
289 | return setupState[key];
290 | }
291 | else if (hasOwn(props, key)) {
292 | return props[key];
293 | }
294 | const publicGetter = publicPropertiesMap[key];
295 | if (publicGetter) {
296 | return publicGetter(instance);
297 | }
298 | },
299 | };
300 |
301 | function initSlots(instance, children) {
302 | const { vnode } = instance;
303 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
304 | normalizeObjectSlots(children, instance.slots);
305 | }
306 | }
307 | function normalizeSlotValue(value) {
308 | return Array.isArray(value) ? value : [value];
309 | }
310 | function normalizeObjectSlots(children, slots) {
311 | for (const key in children) {
312 | const value = children[key];
313 | if (typeof value === "function") {
314 | slots[key] = (props) => normalizeSlotValue(value(props));
315 | }
316 | }
317 | }
318 |
319 | function createComponentInstance(vnode, parent) {
320 | const component = {
321 | vnode,
322 | type: vnode.type,
323 | setupState: {},
324 | props: {},
325 | slots: {},
326 | provides: parent ? parent.provides : {},
327 | parent,
328 | isMounted: false,
329 | subTree: {},
330 | next: null,
331 | emit: () => { },
332 | };
333 | component.emit = emit.bind(null, component);
334 | return component;
335 | }
336 | function setupComponent(instance) {
337 | initProps(instance, instance.vnode.props);
338 | initSlots(instance, instance.vnode.children);
339 | setupStatefulComponent(instance);
340 | }
341 | function setupStatefulComponent(instance) {
342 | const Component = instance.type;
343 | instance.proxy = new Proxy({
344 | _: instance,
345 | }, PublicInstanceProxyHandlers);
346 | const { setup } = Component;
347 | if (setup) {
348 | setCurrentInstance(instance);
349 | const setupResult = setup(shallowReadonly(instance.props), {
350 | emit: instance.emit
351 | });
352 | handleSetupResult(instance, setupResult);
353 | setCurrentInstance(null);
354 | }
355 | }
356 | function handleSetupResult(instance, setupResult) {
357 | if (typeof setupResult === "object") {
358 | instance.setupState = proxyRefs(setupResult);
359 | }
360 | finishComponentSetup(instance);
361 | }
362 | function finishComponentSetup(instance) {
363 | const Component = instance.type;
364 | instance.render = Component.render;
365 | }
366 | let currentInstance = null;
367 | function getCurrentInstance() {
368 | return currentInstance;
369 | }
370 | function setCurrentInstance(instance) {
371 | currentInstance = instance;
372 | }
373 |
374 | function provide(key, value) {
375 | const currentInstance = getCurrentInstance();
376 | if (currentInstance) {
377 | let { provides } = currentInstance;
378 | const parentProvides = currentInstance.parent.provides;
379 | if (provides === parentProvides) {
380 | provides = currentInstance.provides = Object.create(parentProvides);
381 | }
382 | provides[key] = value;
383 | }
384 | }
385 | function inject(key, defaultValue) {
386 | const currentInstance = getCurrentInstance();
387 | if (currentInstance) {
388 | const parentProvides = currentInstance.parent.provides;
389 | if (parentProvides[key]) {
390 | return parentProvides[key];
391 | }
392 | else if (defaultValue) {
393 | if (typeof defaultValue === 'function') {
394 | return defaultValue();
395 | }
396 | return defaultValue;
397 | }
398 | }
399 | }
400 |
401 | function shouldUpdateComponent(prevVnode, nextVnode) {
402 | const { props: prevProps } = prevVnode;
403 | const { props: nextProps } = nextVnode;
404 | for (const key in nextProps) {
405 | if (nextProps[key] !== prevProps[key]) {
406 | return true;
407 | }
408 | }
409 | return false;
410 | }
411 |
412 | function createAppAPI(render) {
413 | return function createApp(rootComponent) {
414 | return {
415 | mount(rootContainer) {
416 | const vnode = createVnode(rootComponent);
417 | render(vnode, rootContainer);
418 | },
419 | };
420 | };
421 | }
422 |
423 | const queue = [];
424 | let isFlushPending = false;
425 | const p = Promise.resolve();
426 | function nextTick(fn) {
427 | return fn ? p.then(fn) : p;
428 | }
429 | function queueJobs(job) {
430 | if (!queue.includes(job)) {
431 | queue.push(job);
432 | }
433 | queueFlush();
434 | }
435 | function queueFlush() {
436 | if (isFlushPending)
437 | return;
438 | isFlushPending = true;
439 | nextTick(flushJobs);
440 | }
441 | function flushJobs() {
442 | isFlushPending = false;
443 | let job;
444 | while ((job = queue.shift())) {
445 | job && job();
446 | }
447 | }
448 |
449 | function createRenderer(options) {
450 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options;
451 | function render(vnode, container, parentComponent) {
452 | patch(null, vnode, container, parentComponent, null);
453 | }
454 | //n1 old n2 new
455 | function patch(n1, n2, container, parentComponent, anchor) {
456 | const { type, shapeFlag } = n2;
457 | switch (type) {
458 | case Fragment:
459 | processFragment(n1, n2, container, parentComponent, anchor);
460 | break;
461 | case Text:
462 | processText(n1, n2, container);
463 | break;
464 | default:
465 | if (shapeFlag & 1 /* ELEMENT */) {
466 | processElement(n1, n2, container, parentComponent, anchor);
467 | }
468 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
469 | processComponent(n1, n2, container, parentComponent, anchor);
470 | }
471 | break;
472 | }
473 | }
474 | function processElement(n1, n2, container, parentComponent, anchor) {
475 | if (!n1) {
476 | mountElement(n1, n2, container, parentComponent, anchor);
477 | }
478 | else {
479 | patchElement(n1, n2, container, parentComponent, anchor);
480 | }
481 | }
482 | function patchElement(n1, n2, container, parentComponent, anchor) {
483 | console.log("patchComponent");
484 | console.log("n1", n1);
485 | console.log("n2", n2);
486 | const oldProps = n1.props || EMPTY_OBJ;
487 | const newProps = n2.props || EMPTY_OBJ;
488 | const el = (n2.el = n1.el);
489 | patchChildren(n1, n2, el, parentComponent, anchor);
490 | patchProps(el, oldProps, newProps);
491 | }
492 | function patchChildren(n1, n2, container, parentComponent, anchor) {
493 | const prevShapeFlag = n1.shapeFlag;
494 | const { shapeFlag } = n2;
495 | const c1 = n1.children;
496 | const c2 = n2.children;
497 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
498 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) {
499 | //1 把n1都清空
500 | unmountChildren(n1.children);
501 | }
502 | //2 设置text
503 | if (c1 !== c2) {
504 | hostSetElementText(container, c2);
505 | }
506 | }
507 | else {
508 | //text to array
509 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) {
510 | hostSetElementText(container, "");
511 | mountChildren(c2, container, parentComponent, anchor);
512 | }
513 | else {
514 | //array
515 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
516 | }
517 | }
518 | }
519 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) {
520 | let i = 0;
521 | const l2 = c2.length;
522 | let e1 = c1.length - 1;
523 | let e2 = l2 - 1;
524 | function isSomeVNodeType(n1, n2) {
525 | return n1.type === n2.type && n1.key === n2.key;
526 | }
527 | //左侧
528 | while (i <= e1 && i <= e2) {
529 | const n1 = c1[i];
530 | const n2 = c2[i];
531 | if (isSomeVNodeType(n1, n2)) {
532 | patch(n1, n2, container, parentComponent, parentAnchor);
533 | }
534 | else {
535 | break;
536 | }
537 | i++;
538 | }
539 | //右侧
540 | while (i <= e1 && i <= e2) {
541 | const n1 = c1[e1];
542 | const n2 = c2[e2];
543 | if (isSomeVNodeType(n1, n2)) {
544 | patch(n1, n2, container, parentComponent, parentAnchor);
545 | }
546 | else {
547 | break;
548 | }
549 | e1--;
550 | e2--;
551 | }
552 | //新的比老的多 创建
553 | if (i > e1) {
554 | if (i <= e2) {
555 | const nextPos = e2 + 1;
556 | const anchor = e2 + 1 < l2 ? c2[nextPos].el : null;
557 | while (i <= e2) {
558 | patch(null, c2[i], container, parentComponent, anchor);
559 | i++;
560 | }
561 | }
562 | }
563 | else if (i > e2) {
564 | while (i <= e1) {
565 | hostRemove(c1[i].el);
566 | i++;
567 | }
568 | }
569 | else {
570 | let s1 = i;
571 | let s2 = i;
572 | const toBePatched = e2 - s2 + 1;
573 | let patched = 0;
574 | const keyToNewIndexMap = new Map();
575 | const newIndexToOldIndexMap = new Array(toBePatched);
576 | let moved = false;
577 | let maxNewIndexSoFar = 0;
578 | for (let i = 0; i < toBePatched; i++)
579 | newIndexToOldIndexMap[i] = 0;
580 | for (let i = s2; i <= e2; i++) {
581 | const nextChild = c2[i];
582 | keyToNewIndexMap.set(nextChild.key, i);
583 | }
584 | for (let i = s1; i <= e1; i++) {
585 | const prevChild = c1[i];
586 | if (patched >= toBePatched) {
587 | hostRemove(prevChild.el);
588 | continue;
589 | }
590 | let newIndex;
591 | if (prevChild.key != null) {
592 | newIndex = keyToNewIndexMap.get(prevChild.key);
593 | }
594 | else {
595 | for (let j = s2; j <= e2; j++) {
596 | if (isSomeVNodeType(prevChild, c2[j])) {
597 | newIndex = j;
598 | break;
599 | }
600 | }
601 | }
602 | if (newIndex === undefined) {
603 | hostRemove(prevChild.el);
604 | }
605 | else {
606 | if (newIndex >= maxNewIndexSoFar) {
607 | maxNewIndexSoFar = newIndex;
608 | }
609 | else {
610 | moved = true;
611 | }
612 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
613 | patch(prevChild, c2[newIndex], container, parentComponent, null);
614 | patched++;
615 | }
616 | }
617 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];
618 | let j = increasingNewIndexSequence.length - 1;
619 | for (let i = toBePatched - 1; i >= 0; i--) {
620 | const nextIndex = i + s2;
621 | const nextChild = c2[nextIndex];
622 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
623 | if (newIndexToOldIndexMap[i] === 0) {
624 | patch(null, nextChild, container, parentComponent, anchor);
625 | }
626 | else if (moved) {
627 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
628 | console.log("移动位置");
629 | hostInsert(nextChild.el, container, anchor);
630 | }
631 | else {
632 | j--;
633 | }
634 | }
635 | }
636 | }
637 | }
638 | function getSequence(arr) {
639 | const p = arr.slice();
640 | const result = [0];
641 | let i, j, u, v, c;
642 | const len = arr.length;
643 | for (i = 0; i < len; i++) {
644 | const arrI = arr[i];
645 | if (arrI !== 0) {
646 | j = result[result.length - 1];
647 | if (arr[j] < arrI) {
648 | p[i] = j;
649 | result.push(i);
650 | continue;
651 | }
652 | u = 0;
653 | v = result.length - 1;
654 | while (u < v) {
655 | c = (u + v) >> 1;
656 | if (arr[result[c]] < arrI) {
657 | u = c + 1;
658 | }
659 | else {
660 | v = c;
661 | }
662 | }
663 | if (arrI < arr[result[u]]) {
664 | if (u > 0) {
665 | p[i] = result[u - 1];
666 | }
667 | result[u] = i;
668 | }
669 | }
670 | }
671 | u = result.length;
672 | v = result[u - 1];
673 | while (u-- > 0) {
674 | result[u] = v;
675 | v = p[v];
676 | }
677 | return result;
678 | }
679 | function unmountChildren(children) {
680 | for (let i = 0; i < children.length; i++) {
681 | const el = children[i].el;
682 | hostRemove(el);
683 | }
684 | }
685 | const EMPTY_OBJ = {};
686 | function patchProps(el, oldProps, newProps) {
687 | if (oldProps !== newProps) {
688 | for (let key in newProps) {
689 | const prevProp = oldProps[key];
690 | const nextProp = newProps[key];
691 | if (prevProp !== nextProp) {
692 | hostPatchProp(el, key, prevProp, nextProp);
693 | }
694 | }
695 | if (oldProps !== EMPTY_OBJ)
696 | for (let key in oldProps) {
697 | if (!(key in newProps)) {
698 | hostPatchProp(el, key, oldProps[key], null);
699 | }
700 | }
701 | }
702 | }
703 | function mountElement(n1, n2, container, parentComponent, anchor) {
704 | const el = (n2.el = hostCreateElement(n2.type));
705 | const { children, shapeFlag } = n2;
706 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
707 | el.textContent = children;
708 | }
709 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
710 | mountChildren(n2.children, el, parentComponent, anchor);
711 | }
712 | const { props } = n2;
713 | for (const key in props) {
714 | const val = props[key];
715 | hostPatchProp(el, key, null, val);
716 | }
717 | hostInsert(el, container, anchor);
718 | }
719 | function processFragment(n1, n2, container, parentComponent, anchor) {
720 | mountChildren(n2.children, container, parentComponent, anchor);
721 | }
722 | function processText(n1, n2, container) {
723 | const { children } = n2;
724 | const textNode = (n2.el = document.createTextNode(children));
725 | container.append(textNode);
726 | }
727 | function mountChildren(children, container, parentComponent, anchor) {
728 | children.forEach((v) => {
729 | patch(null, v, container, parentComponent, anchor);
730 | });
731 | }
732 | function processComponent(n1, n2, container, parentComponent, anchor) {
733 | if (!n1) {
734 | mountComponent(n2, container, parentComponent, anchor);
735 | }
736 | else {
737 | updateComponent(n1, n2);
738 | }
739 | }
740 | function updateComponent(n1, n2) {
741 | const instance = (n2.component = n1.component);
742 | if (shouldUpdateComponent(n1, n2)) {
743 | instance.next = n2;
744 | instance.update();
745 | }
746 | else {
747 | n2.component = n1.component;
748 | n2.el = n1.el;
749 | instance.vnode = n2;
750 | }
751 | }
752 | function mountComponent(initialVnode, container, parentComponent, anchor) {
753 | const instance = (initialVnode.component = createComponentInstance(initialVnode, parentComponent));
754 | setupComponent(instance);
755 | setupRenderEffect(instance, initialVnode, container, anchor);
756 | }
757 | function setupRenderEffect(instance, initialVnode, container, anchor) {
758 | instance.update = effect(() => {
759 | if (!instance.isMounted) {
760 | console.log("init");
761 | const { proxy } = instance;
762 | const subTree = (instance.subTree = instance.render.call(proxy));
763 | patch(null, subTree, container, instance, anchor);
764 | initialVnode.el = subTree.el;
765 | instance.isMounted = true;
766 | }
767 | else {
768 | console.log("update");
769 | const { next, vnode } = instance;
770 | if (next) {
771 | next.el = vnode.el;
772 | updateComponentPreRender(instance, next);
773 | }
774 | const { proxy } = instance;
775 | const subTree = instance.render.call(proxy);
776 | const prevSubTree = instance.subTree;
777 | instance.subTree = subTree;
778 | patch(prevSubTree, subTree, container, instance, anchor);
779 | }
780 | }, {
781 | scheduler() {
782 | console.log('update--scheduler');
783 | queueJobs(instance.update);
784 | }
785 | });
786 | }
787 | return {
788 | createApp: createAppAPI(render)
789 | };
790 | }
791 | function updateComponentPreRender(instance, nextVnode) {
792 | instance.vnode = nextVnode;
793 | instance.next = null;
794 | instance.props = nextVnode.props;
795 | }
796 |
797 | function createElement(type) {
798 | return document.createElement(type);
799 | }
800 | function patchProp(el, key, oldVal, newVal) {
801 | const isOn = (key) => /^on[A-Z]/.test(key);
802 | if (isOn(key)) {
803 | const event = key.slice(2).toLowerCase();
804 | el.addEventListener(event, newVal);
805 | }
806 | else {
807 | if (newVal === undefined || newVal === null) {
808 | el.removeAttribute(key, newVal);
809 | }
810 | else {
811 | el.setAttribute(key, newVal);
812 | }
813 | }
814 | }
815 | function insert(child, parent, anchor) {
816 | parent.insertBefore(child, anchor || null);
817 | }
818 | function remove(child) {
819 | const parent = child.parentNode;
820 | if (parent) {
821 | parent.removeChild(child);
822 | }
823 | }
824 | function setElementText(container, text) {
825 | container.textContent = text;
826 | }
827 | const render = createRenderer({
828 | createElement,
829 | patchProp,
830 | insert,
831 | remove,
832 | setElementText
833 | });
834 | function createApp(...args) {
835 | return render.createApp(...args);
836 | }
837 |
838 | export { createApp, createRenderer, createTextVnode, getCurrentInstance, h, inject, nextTick, provide, proxyRefs, ref, renderSlots };
839 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "GUIDE-MINI-VUE",
3 | "version": "1.0.0",
4 | "main": "lib/guide-mini-vue.cjs.js",
5 | "module":"lib/guide-mini-vue.esm.js",
6 | "license": "MIT",
7 | "scripts": {
8 | "test": "jest",
9 | "build": "rollup -c rollup.config.js"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "^7.17.7",
13 | "@babel/preset-env": "^7.16.11",
14 | "@babel/preset-typescript": "^7.16.7",
15 | "@rollup/plugin-typescript": "^8.3.1",
16 | "@types/jest": "^27.4.1",
17 | "babel-jest": "^27.5.1",
18 | "jest": "^27.5.1",
19 | "rollup": "^2.70.1",
20 | "tslib": "^2.3.1",
21 | "typescript": "^4.6.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from "@rollup/plugin-typescript";
2 | import pkg from './package.json';
3 | export default{
4 | input:"./src/index.ts",
5 | output:[
6 | {
7 | format:"cjs",
8 | file:pkg.main
9 | },
10 | {
11 | format: "es",
12 | file: pkg.module
13 | }
14 | ],
15 | plugins:[
16 | typescript()
17 | ]
18 | }
--------------------------------------------------------------------------------
/src/compiler-core/src/ast.ts:
--------------------------------------------------------------------------------
1 | export const enum NodeTypes{
2 | INTERPOLATION,
3 | SIMPLE_EXPRESSION,
4 | ELEMENT,
5 | TEXT
6 | }
--------------------------------------------------------------------------------
/src/compiler-core/src/codegen.ts:
--------------------------------------------------------------------------------
1 | export function generate(ast){
2 | const context = createCodegenContext();
3 | const { push } = context;
4 | push(`return `);
5 | const functionName = "render";
6 | const args = ["_ctx", "_cache"];
7 | const signature = args.join(",");
8 | push(`function ${functionName} (${signature}){`);
9 | push(`return `);
10 | genNode(ast.codegenNode, context);
11 | push(`}`);
12 | return {
13 | code:context.code,
14 | };
15 | }
16 | function createCodegenContext(){
17 | const context = {
18 | code:"",
19 | push(source){
20 | context.code += source;
21 | }
22 | }
23 | return context;
24 | }
25 | function genNode(node,context){
26 | const {push} = context;
27 | push(`'${node.content}'`);
28 | }
--------------------------------------------------------------------------------
/src/compiler-core/src/parse.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 |
3 | const enum TagType{
4 | Start,
5 | End
6 | }
7 |
8 | export function baseParse(content:string){
9 | const context = createParserContext(content);
10 | return createRoot(parseChildren(context,[]));
11 | }
12 | function parseChildren(context,ancestors){
13 | const nodes:any = [];
14 | while(!isEnd(context,ancestors)){
15 | let node;
16 | const s = context.source;
17 | if(s.startsWith("{{")){
18 | node = parseInterpolation(context);
19 | }else if(s[0]==='<'){
20 | if(/[a-z]/i.test(s[1])){
21 | node = parseElement(context,ancestors);
22 | }
23 | }
24 | if(!node){
25 | console.log('parse text');
26 | node = parseText(context);
27 | }
28 | nodes.push(node);
29 | }
30 | return nodes;
31 | }
32 | function isEnd(context,ancestors){
33 | const s = context.source;
34 | if(s.startsWith("")){
35 | for(let i = ancestors.length-1;i>=0;i--){
36 | const tag = ancestors[i].tag;
37 | if(startsWithEndTagOpen(s,tag)){
38 | return true;
39 | }
40 | }
41 | }
42 | return !s;
43 | }
44 | function parseText(context:any){
45 | let endIndex = context.source.length;
46 | let endTokens = ["<","{{"];
47 | for(let i = 0;i < endTokens.length;i++){
48 | const index = context.source.indexOf(endTokens[i]);
49 | if(index!==-1 && endIndex > index){
50 | endIndex = index;
51 | }
52 | }
53 | const content = parseTextData(context,endIndex);
54 | console.log('content---',content);
55 | return{
56 | type:NodeTypes.TEXT,
57 | content
58 | }
59 | }
60 | function parseTextData(context:any,length){
61 | const content = context.source.slice(0,length);
62 | advanceBy(context,length);
63 | return content;
64 | }
65 | function parseElement(context:any,ancestors){
66 | const element:any = parseTag(context,TagType.Start);
67 | ancestors.push(element);
68 | element.children = parseChildren(context,ancestors);
69 | ancestors.pop();
70 | if(startsWithEndTagOpen(context.source,element.tag)){
71 | parseTag(context,TagType.End);
72 | }else{
73 | throw new Error(`缺少结束标签${element.tag}`);
74 | }
75 | return element;
76 | }
77 | function startsWithEndTagOpen(source,tag){
78 | return source.slice(2,2+tag.length).toLowerCase() === tag.toLowerCase();
79 | }
80 | function parseTag(context:any,type:TagType){
81 | const match:any = /^<\/?([a-z]*)/i.exec(context.source);
82 | const tag = match[1];
83 | advanceBy(context,match[0].length);
84 | advanceBy(context,1);
85 | if(type === TagType.End) return;
86 | return {
87 | type:NodeTypes.ELEMENT,
88 | tag
89 | }
90 | }
91 | function parseInterpolation(context){
92 | const openDelimiter = "{{";
93 | const closeDelimiter = "}}";
94 | const closeIndex = context.source.indexOf(closeDelimiter,openDelimiter.length);
95 | advanceBy(context,openDelimiter.length);
96 | const rawContentLength = closeIndex-openDelimiter.length;
97 | const rawcontent = parseTextData(context,rawContentLength);
98 | const content = rawcontent.trim();
99 | advanceBy(context,closeDelimiter.length);
100 | return {
101 | type:NodeTypes.INTERPOLATION,
102 | content:{
103 | type:NodeTypes.SIMPLE_EXPRESSION,
104 | content
105 | }
106 | }
107 | }
108 | function advanceBy(context:any,length:number){
109 | context.source = context.source.slice(length);
110 | }
111 | function createRoot(children){
112 | return {
113 | children
114 | }
115 | }
116 | function createParserContext(content){
117 | return {
118 | source:content
119 | }
120 | }
--------------------------------------------------------------------------------
/src/compiler-core/src/transform.ts:
--------------------------------------------------------------------------------
1 | export function transform(root,options = {}){
2 | //深度优先遍历
3 | const context = createTransformContext(root, options);
4 | traverseNode(root, context);
5 | createRootCodegen(root);
6 | }
7 | function createRootCodegen(root){
8 | root.codegenNode = root.children[0];
9 | }
10 | function createTransformContext(root, options) {
11 | const context = {
12 | root,
13 | nodeTransforms: options.nodeTransforms||[]
14 | };
15 | return context;
16 | }
17 | function traverseNode(node:any,context){
18 | const nodeTransforms = context.nodeTransforms;
19 | for(let i = 0;i{
5 | it("string",()=>{
6 | const ast = baseParse("hi");
7 | transform(ast)
8 | const {code} = generate(ast);
9 | expect(code).toMatchSnapshot();
10 | })
11 | it("interpolation",()=>{
12 |
13 | })
14 | })
--------------------------------------------------------------------------------
/src/compiler-core/tests/parse.spec.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from '../src/ast';
2 | import {baseParse} from '../src/parse';
3 | describe("Parse",()=>{
4 | describe("interpolation",()=>{
5 | test("simple interpolation",()=>{
6 | const ast = baseParse("{{ message }}");
7 | expect(ast.children[0]).toStrictEqual({
8 | type:NodeTypes.INTERPOLATION,
9 | content:{
10 | type:NodeTypes.SIMPLE_EXPRESSION,
11 | content:"message"
12 | }
13 | })
14 | })
15 | })
16 | })
17 | describe("element",()=>{
18 | it("simple element div",()=>{
19 | const ast = baseParse("");
20 | expect(ast.children[0]).toStrictEqual({
21 | type:NodeTypes.ELEMENT,
22 | tag:"div",
23 | children:[]
24 | })
25 | })
26 | })
27 | describe("element",()=>{
28 | it("simple text",()=>{
29 | const ast = baseParse("some text");
30 | expect(ast.children[0]).toStrictEqual({
31 | type:NodeTypes.TEXT,
32 | content:"some text"
33 | })
34 | })
35 | })
36 | test("hello world",()=>{
37 | const ast = baseParse("hi,{{message}}
");
38 | expect(ast.children[0]).toStrictEqual({
39 | type:NodeTypes.ELEMENT,
40 | tag:"p",
41 | children:[
42 | {
43 | type:NodeTypes.TEXT,
44 | content:"hi,"
45 | },
46 | {
47 | type:NodeTypes.INTERPOLATION,
48 | content:{
49 | type:NodeTypes.SIMPLE_EXPRESSION,
50 | content:"message"
51 | }
52 | }
53 | ]
54 | })
55 | })
56 | test("Nested element",()=>{
57 | const ast = baseParse("");
58 | expect(ast.children[0]).toStrictEqual({
59 | type:NodeTypes.ELEMENT,
60 | tag:"div",
61 | children:[
62 | {
63 | type:NodeTypes.ELEMENT,
64 | tag:"p",
65 | children:[
66 | {
67 | type:NodeTypes.TEXT,
68 | content:"hi",
69 | }
70 | ]
71 | },
72 | {
73 | type:NodeTypes.INTERPOLATION,
74 | content:{
75 | type:NodeTypes.SIMPLE_EXPRESSION,
76 | content:"message"
77 | }
78 | }
79 | ]
80 | })
81 | })
82 | test("should throw error when lack end tag",()=>{
83 | expect(()=>{
84 | baseParse("
")
85 | }).toThrow('缺少结束标签span');
86 | })
--------------------------------------------------------------------------------
/src/compiler-core/tests/transform.spec.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../src/ast";
2 | import { baseParse } from "../src/parse";
3 | import {transform} from '../src/transform';
4 |
5 | describe("transform",()=>{
6 | it("happy path",()=>{
7 | const ast = baseParse("hi,{{message}}
");
8 | const plugin = (node)=>{
9 | if (node.type === NodeTypes.TEXT) {
10 | node.content = node.content + "mini-vue";
11 | }
12 | }
13 | transform(ast,{
14 | nodeTransforms:[plugin]
15 | });
16 | const nodeText = ast.children[0].children[0];
17 | expect(nodeText.content).toBe("hi,mini-vue")
18 | })
19 | })
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './runtime-dom';
2 | export * from './reactivity';
--------------------------------------------------------------------------------
/src/reactivity/baseHandlers.ts:
--------------------------------------------------------------------------------
1 | import { extend, isObject } from "../shared";
2 | import { track, trigger } from "./effect";
3 | import { reactive, ReactiveFlags, readonly } from "./reactive";
4 | const get = createGetter();
5 | const set = createSetter();
6 | const readonlyGet = createGetter(true);
7 | const shallowReadonlyGet = createGetter(true,true);
8 | function createGetter(isReadonly = false,shallow = false) {
9 | return function get(target, key) {
10 | if (key === ReactiveFlags.IS_REACTIVE) {
11 | return !isReadonly;
12 | }else if(key === ReactiveFlags.IS_READONLY){
13 | return isReadonly;
14 | }
15 | const res = Reflect.get(target, key);
16 | if(shallow) return res;
17 | if (isObject(res)) {
18 | return isReadonly ? readonly(res) : reactive(res);
19 | }
20 | if (!isReadonly) {
21 | track(target, key);
22 | }
23 | return res;
24 | };
25 | }
26 | function createSetter() {
27 | return function set(target, key, value) {
28 | const res = Reflect.set(target, key, value);
29 | trigger(target, key);
30 | return res;
31 | };
32 | }
33 |
34 | export const mutableHandlers = {
35 | get,
36 | set,
37 | };
38 | export const readonlyHandlers = {
39 | get: readonlyGet,
40 | set(target, key, value) {
41 | console.warn(`key:${key} set失败了,因为target是readonly的`,target)
42 | return true;
43 | },
44 | };
45 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
46 | get: shallowReadonlyGet
47 | });
--------------------------------------------------------------------------------
/src/reactivity/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from "./effect";
2 |
3 | class ComputedRefImpl{
4 | private _getter:any;
5 | private _dirty: boolean = true;
6 | private _value: any;
7 | private _effect: ReactiveEffect;
8 | constructor(getter: any){
9 | this._getter = getter;
10 | this._effect = new ReactiveEffect(getter,()=>{
11 | if (!this._dirty) this._dirty = true;
12 | });
13 | }
14 | get value(){
15 | if(this._dirty){
16 | this._dirty = false;
17 | this._value = this._effect.run();
18 | }
19 | return this._value;
20 | }
21 | }
22 | export function computed(getter){
23 | return new ComputedRefImpl(getter);
24 | }
--------------------------------------------------------------------------------
/src/reactivity/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from "../shared/index";
2 |
3 | let activeEffect: any;
4 | let shouldTrack;
5 | export class ReactiveEffect{
6 | private _fn: any;
7 | deps = [];
8 | active = true;
9 | onStop?:()=>void;
10 | constructor(fn: any,public scheduler?: any){
11 | this._fn = fn;
12 | }
13 | run(){
14 | if(!this.active){
15 | return this._fn();
16 | }
17 | shouldTrack = true;
18 |
19 | activeEffect = this;
20 | const result = this._fn();
21 |
22 | shouldTrack = false;
23 | return result;
24 | }
25 | stop(){
26 | if(this.active){
27 | cleanupEffect(this)
28 | if(this.onStop) this.onStop();
29 | this.active = false;
30 | }
31 | }
32 | }
33 | function cleanupEffect(effect){
34 | effect.deps.forEach((dep:any)=>{
35 | dep.delete(effect)
36 | })
37 | effect.deps.length = 0;
38 | }
39 | let targetMap = new Map();
40 | export function track(target: any, key: any) {
41 | //targetMap target key
42 | if(!isTracking()) return;
43 | let depsMap = targetMap.get(target);
44 | if (!depsMap) {
45 | depsMap = new Map();
46 | targetMap.set(target, depsMap);
47 | }
48 | let deps = depsMap.get(key);
49 | if (!deps) {
50 | deps = new Set();
51 | depsMap.set(key, deps);
52 | }
53 | trackEffects(deps);
54 | }
55 | export function trackEffects(deps){
56 | if (deps.has(activeEffect)) return;
57 | deps.add(activeEffect);
58 | //反向收集
59 | activeEffect.deps.push(deps);
60 | }
61 | export function isTracking() {
62 | return shouldTrack && activeEffect!==undefined;
63 | }
64 | export function trigger(target:any,key:any){
65 | let depsMap = targetMap.get(target);
66 | if(!depsMap) return;
67 | let deps = depsMap.get(key);
68 | triggerEffect(deps);
69 | }
70 | export function triggerEffect(deps){
71 | deps && deps.forEach((effect)=>{
72 | if(effect.scheduler){
73 | effect.scheduler()
74 | }else{
75 | effect.run()
76 | }
77 | })
78 | }
79 | export function effect(fn: any,options:any = {}){
80 | const _effect = new ReactiveEffect(fn,options.scheduler);
81 | extend(_effect,options);
82 | _effect.onStop = options.onStop;
83 | _effect.run();
84 | const runner:any = _effect.run.bind(_effect);
85 | runner.effect = _effect;
86 | return runner;
87 | }
88 | export function stop(runner){
89 | runner.effect.stop()
90 | }
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
1 | export { ref, proxyRefs } from "./ref";
--------------------------------------------------------------------------------
/src/reactivity/reactive.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from './../shared/index';
2 | import { mutableHandlers, readonlyHandlers,shallowReadonlyHandlers } from "./baseHandlers";
3 | export const enum ReactiveFlags{
4 | IS_REACTIVE='__v_isReactive',
5 | IS_READONLY='__v_isReadonly'
6 | }
7 | export function reactive(raw: any) {
8 | return createActiveObject(raw,mutableHandlers);
9 | }
10 | export function readonly(raw:any){
11 | return createActiveObject(raw, readonlyHandlers);
12 | }
13 | export function shallowReadonly(raw:any){
14 | return createActiveObject(raw, shallowReadonlyHandlers);
15 | }
16 | export function isReactive(value){
17 | return !!value[ReactiveFlags.IS_REACTIVE];
18 | }
19 | export function isReadonly(value){
20 | return !!value[ReactiveFlags.IS_READONLY];
21 | }
22 | export function isProxy(value){
23 | return isReactive(value) || isReadonly(value);
24 | }
25 | function createActiveObject(target: any,baseHandlers) {
26 | if (!isObject(target)) {
27 | console.warn(`target ${target} 必须是一个对象`);
28 | return target;
29 | }
30 | return new Proxy(target, baseHandlers);
31 | }
32 |
--------------------------------------------------------------------------------
/src/reactivity/ref.ts:
--------------------------------------------------------------------------------
1 | import { hasChanged, isObject } from "../shared";
2 | import { isTracking, trackEffects, triggerEffect } from "./effect";
3 | import { reactive } from "./reactive";
4 |
5 | class RefImpl{
6 | private _value:any;
7 | public dep;
8 | private _rawValue: any;
9 | private _v_ref: boolean;
10 | constructor(value){
11 | this._v_ref = true;
12 | this._rawValue = value;
13 | this._value = convert(value);
14 | this.dep = new Set();
15 | }
16 | get value(){
17 | trackRefEffects(this);
18 | return this._value;
19 | }
20 | set value(newValue){
21 | if (hasChanged(newValue, this._rawValue)){
22 | this._rawValue = newValue;
23 | this._value = convert(newValue);
24 | triggerEffect(this.dep);
25 | }
26 | }
27 | }
28 | function convert(value){
29 | return isObject(value) ? reactive(value) : value;
30 | }
31 | function trackRefEffects(ref){
32 | if (isTracking()) {
33 | trackEffects(ref.dep);
34 | }
35 | }
36 | export function ref(value){
37 | return new RefImpl(value);
38 | }
39 | export function isRef(value){
40 | return !!value._v_ref;
41 | }
42 | export function unRef(ref){
43 | return isRef(ref) ? ref.value: ref;
44 | }
45 | export function proxyRefs(objectWithRefs){
46 | return new Proxy(objectWithRefs,{
47 | get(target,key){
48 | return unRef(Reflect.get(target,key));
49 | },
50 | set(target,key,value){
51 | if(isRef(target[key]) && !isRef(value)){
52 | return (target[key].value = value);
53 | }else{
54 | return Reflect.set(target, key, value);
55 | }
56 | }
57 | });
58 | }
--------------------------------------------------------------------------------
/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(()=>{
10 | return user.age;
11 | })
12 | expect(age.value).toBe(1);
13 | })
14 | it("should computed lazily",()=>{
15 | const value = reactive({
16 | foo:1
17 | })
18 | const getter = jest.fn(()=>{
19 | return value.foo;
20 | })
21 | const cValue = computed(getter);
22 | //lazy
23 | expect(getter).not.toHaveBeenCalled();
24 | expect(cValue.value).toBe(1);
25 | expect(getter).toHaveBeenCalledTimes(1);
26 | //触发get 只调用一次
27 | cValue.value;
28 | expect(getter).toHaveBeenCalledTimes(1);
29 |
30 | value.foo = 2;
31 | expect(getter).toHaveBeenCalledTimes(1);
32 |
33 | expect(cValue.value).toBe(2);
34 | expect(getter).toHaveBeenCalledTimes(2);
35 |
36 | cValue.value;
37 | expect(getter).toHaveBeenCalledTimes(2);
38 | })
39 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import {reactive} from '../reactive';
2 | import {effect,stop} from '../effect';
3 | describe('effect',()=>{
4 | it('happy path',()=>{
5 | const user = reactive({
6 | age:10
7 | })
8 | let nextAge;
9 | effect(()=>{
10 | nextAge = user.age+1;
11 | })
12 | expect(nextAge).toBe(11);
13 | user.age++;
14 | expect(nextAge).toBe(12);
15 | })
16 | it('should return runner when call effect',()=>{
17 | let foo = 10;
18 | const runner = effect(()=>{
19 | foo++;
20 | return 'foo';
21 | })
22 | expect(foo).toBe(11);
23 | const r = runner();
24 | expect(foo).toBe(12);
25 | expect(r).toBe('foo');
26 | })
27 | it('scheduler',()=>{
28 | let dummy;
29 | let run:any;
30 | const scheduler = jest.fn(()=>{
31 | run = runner;
32 | })
33 | const obj = reactive({foo:1});
34 | const runner = effect(
35 | ()=>{
36 | dummy = obj.foo;
37 | },{
38 | scheduler
39 | }
40 | )
41 | expect(scheduler).not.toHaveBeenCalled();
42 | expect(dummy).toBe(1);
43 | obj.foo++;
44 | expect(dummy).toBe(1);
45 | expect(scheduler).toHaveBeenCalledTimes(1);
46 | run();
47 | expect(dummy).toBe(2);
48 | })
49 | it("stop",()=>{
50 | let dummy;
51 | const obj = reactive({prop:1});
52 | const runner = effect(()=>{
53 | dummy = obj.prop;
54 | })
55 | obj.prop = 2;
56 | expect(dummy).toBe(2);
57 | stop(runner);
58 | // obj.prop = 3;
59 | //get set obj.prop = obj.prop+1;
60 | obj.prop++;
61 | expect(dummy).toBe(2);
62 | runner();
63 | expect(dummy).toBe(3);
64 | })
65 | it('onStop',()=>{
66 | const obj = reactive({foo:1});
67 | let dummy;
68 | const onStop = jest.fn();
69 | const runner = effect(
70 | ()=>{
71 | dummy = obj.foo;
72 | },{
73 | onStop
74 | }
75 | )
76 | stop(runner);
77 | expect(onStop).toBeCalledTimes(1);
78 | })
79 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import {isProxy, isReactive, reactive} from '../reactive'
2 | describe('reactive',()=>{
3 | it('happy path',()=>{
4 | const original = {foo:1};
5 | const observed = reactive(original);
6 | expect(observed).not.toBe(original);
7 | expect(observed.foo).toBe(1);
8 | expect(isReactive(observed)).toBe(true);
9 | expect(isReactive(original)).toBe(false);
10 | expect(isProxy(observed)).toBe(true);
11 | })
12 | test('nested reactive',()=>{
13 | const original = {
14 | nested:{
15 | foo:1
16 | },
17 | array:[{bar:2}]
18 | }
19 | const observed = reactive(original);
20 | expect(isReactive(observed.nested)).toBe(true);
21 | expect(isReactive(observed.array)).toBe(true);
22 | expect(isReactive(observed.array[0])).toBe(true);
23 | })
24 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isProxy, isReadonly, readonly } from "../reactive";
2 |
3 | describe('readonly',()=>{
4 | it('happy path',()=>{
5 | const original = {foo:1,bar:{baz:2}};
6 | const wrapped = readonly(original);
7 | expect(isReadonly(wrapped)).toBe(true);
8 | expect(isReadonly(original)).toBe(false);
9 | expect(isReadonly(wrapped.bar)).toBe(true);
10 | expect(wrapped).not.toBe(original);
11 | expect(isProxy(wrapped)).toBe(true);
12 | expect(wrapped.foo).toBe(1);
13 | })
14 | it('warn then call set',()=>{
15 | console.warn = jest.fn();
16 | const user = readonly({age:10});
17 | user.age = 11;
18 | expect(console.warn).toBeCalled();
19 | })
20 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../effect";
2 | import { reactive } from "../reactive";
3 | import { isRef, proxyRefs, ref, unRef } from "../ref";
4 |
5 | describe("ref",()=>{
6 | it("happy path",()=>{
7 | const a = ref(1);
8 | expect(a.value).toBe(1);
9 | })
10 | it("should be reactive",()=>{
11 | const a = ref(1);
12 | let dummy;
13 | let calls = 0;
14 | effect(()=>{
15 | calls++;
16 | dummy = a.value;
17 | })
18 | expect(calls).toBe(1);
19 | expect(dummy).toBe(1);
20 | a.value = 2;
21 | expect(calls).toBe(2);
22 | expect(dummy).toBe(2);
23 | // same value should not trigger
24 | a.value = 2;
25 | expect(calls).toBe(2);
26 | expect(dummy).toBe(2);
27 | })
28 | it("should make nested properties reactive",()=>{
29 | const a = ref({
30 | count:1
31 | });
32 | let dummy;
33 | effect(()=>{
34 | dummy = a.value.count;
35 | })
36 | expect(dummy).toBe(1);
37 | a.value.count = 2;
38 | expect(dummy).toBe(2);
39 | })
40 | it("isRef",()=>{
41 | const a = ref(1);
42 | const user = reactive({age:1});
43 | expect(isRef(a)).toBe(true);
44 | expect(isRef(1)).toBe(false);
45 | expect(isRef(user)).toBe(false);
46 | })
47 | it("unRef", () => {
48 | const a = ref(1);
49 | expect(unRef(a)).toBe(1);
50 | expect(unRef(1)).toBe(1);
51 | });
52 | it("proxyRefs",()=>{
53 | const user = {
54 | age:ref(10),
55 | name:"xiaohong"
56 | }
57 | const proxyUser = proxyRefs(user);
58 | expect(user.age.value).toBe(10);
59 | expect(proxyUser.age).toBe(10);
60 | expect(proxyUser.name).toBe("xiaohong");
61 | proxyUser.age = 20;
62 | expect(proxyUser.age).toBe(20);
63 | expect(user.age.value).toBe(20);
64 |
65 | proxyUser.age = ref(10);
66 | expect(proxyUser.age).toBe(10);
67 | expect(user.age.value).toBe(10);
68 |
69 | })
70 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadonly, shallowReadonly } from "../reactive";
2 |
3 | describe('shallowReadonly',()=>{
4 | test("should not make non-reactive properties reactive",()=>{
5 | const props = shallowReadonly({n:1,foo:1});
6 | expect(isReadonly(props)).toBe(true);
7 | expect(isReadonly(props.n)).toBe(false);
8 | })
9 | it("should call console.warn when set",()=>{
10 | console.warn = jest.fn();
11 | const user = shallowReadonly({
12 | age:10
13 | })
14 | user.age = 11;
15 | expect(console.warn).toHaveBeenCalled();
16 | })
17 | })
--------------------------------------------------------------------------------
/src/runtime-core/apiInject.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance } from "./component";
2 | export function provide(key,value){
3 | const currentInstance:any = getCurrentInstance();
4 | if(currentInstance){
5 | let {provides} = currentInstance;
6 | const parentProvides = currentInstance.parent.provides;
7 | if(provides===parentProvides){
8 | provides = currentInstance.provides = Object.create(parentProvides);
9 | }
10 | provides[key] = value;
11 | }
12 | }
13 | export function inject(key,defaultValue){
14 | const currentInstance:any = getCurrentInstance();
15 | if (currentInstance) {
16 | const parentProvides = currentInstance.parent.provides;
17 | if (parentProvides[key]){
18 | return parentProvides[key];
19 | } else if(defaultValue){
20 | if(typeof defaultValue === 'function'){
21 | return defaultValue()
22 | }
23 | return defaultValue;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/runtime-core/component.ts:
--------------------------------------------------------------------------------
1 | import { proxyRefs } from "../reactivity";
2 | import { shallowReadonly } from "../reactivity/reactive";
3 | import { emit } from "./componentEmit";
4 | import { initProps } from "./componentProps";
5 | import { PublicInstanceProxyHandlers } from "./componentPublicInstance";
6 | import { initSlots } from "./componentSlots";
7 |
8 | export function createComponentInstance(vnode,parent){
9 | const component = {
10 | vnode,
11 | type: vnode.type,
12 | setupState: {},
13 | props: {},
14 | slots: {},
15 | provides: parent ? parent.provides : {},
16 | parent,
17 | isMounted: false,
18 | subTree:{},
19 | next:null,
20 | emit: () => {},
21 | };
22 | component.emit = emit.bind(null,component) as any;
23 | return component;
24 | }
25 | export function setupComponent(instance){
26 | initProps(instance,instance.vnode.props);
27 | initSlots(instance,instance.vnode.children);
28 | setupStatefulComponent(instance);
29 | }
30 | function setupStatefulComponent(instance) {
31 | const Component = instance.type;
32 | instance.proxy = new Proxy(
33 | {
34 | _: instance,
35 | },
36 | PublicInstanceProxyHandlers
37 | );
38 | const { setup } = Component;
39 | if(setup){
40 | setCurrentInstance(instance);
41 | const setupResult = setup(shallowReadonly(instance.props),{
42 | emit:instance.emit
43 | });
44 | handleSetupResult(instance,setupResult);
45 | setCurrentInstance(null);
46 | }
47 | }
48 | function handleSetupResult(instance,setupResult) {
49 | if (typeof setupResult === "object") {
50 | instance.setupState = proxyRefs(setupResult);
51 | }
52 | finishComponentSetup(instance);
53 | }
54 | function finishComponentSetup(instance){
55 | const Component = instance.type;
56 | instance.render = Component.render;
57 | }
58 | let currentInstance = null;
59 | export function getCurrentInstance(){
60 | return currentInstance;
61 | }
62 | export function setCurrentInstance(instance){
63 | currentInstance = instance;
64 | }
--------------------------------------------------------------------------------
/src/runtime-core/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { camelize, toHandlerKey } from "../shared/index";
2 |
3 | export function emit(instance,event,...args){
4 | const { props } = instance;
5 | const HandlerName = toHandlerKey(camelize(event));
6 | const handler = props[HandlerName];
7 | handler && handler(...args)
8 | }
--------------------------------------------------------------------------------
/src/runtime-core/componentProps.ts:
--------------------------------------------------------------------------------
1 | export function initProps(instance,rawProps = {}){
2 | instance.props = rawProps;
3 | }
--------------------------------------------------------------------------------
/src/runtime-core/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | const publicPropertiesMap = {
2 | $el: (i) => i.vnode.el,
3 | $slots:(i)=> i.slots,
4 | $props:(i)=> i.props
5 | };
6 | export const PublicInstanceProxyHandlers = {
7 | get({ _: instance }, key) {
8 | const { setupState,props } = instance;
9 | const hasOwn = (val,key)=>Object.prototype.hasOwnProperty.call(val,key);
10 | if (hasOwn(setupState, key)) {
11 | return setupState[key];
12 | } else if (hasOwn(props, key)) {
13 | return props[key];
14 | }
15 | const publicGetter = publicPropertiesMap[key];
16 | if (publicGetter) {
17 | return publicGetter(instance);
18 | }
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/src/runtime-core/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/shapeFlags";
2 | export function initSlots(instance, children) {
3 | const { vnode } = instance;
4 | if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN){
5 | normalizeObjectSlots(children, instance.slots);
6 | }
7 | }
8 | function normalizeSlotValue(value) {
9 | return Array.isArray(value) ? value : [value];
10 | }
11 | function normalizeObjectSlots(children, slots) {
12 | for (const key in children) {
13 | const value = children[key];
14 | if (typeof value === "function") {
15 | slots[key] = (props) => normalizeSlotValue(value(props));
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/runtime-core/componentUpdateUtils.ts:
--------------------------------------------------------------------------------
1 | export function shouldUpdateComponent(prevVnode,nextVnode){
2 | const { props: prevProps } = prevVnode;
3 | const { props: nextProps } = nextVnode;
4 |
5 | for (const key in nextProps) {
6 | if (nextProps[key] !== prevProps[key]) {
7 | return true;
8 | }
9 | }
10 | return false;
11 | }
--------------------------------------------------------------------------------
/src/runtime-core/createApp.ts:
--------------------------------------------------------------------------------
1 | import { createVnode } from "./vnode";
2 |
3 | export function createAppAPI(render){
4 | return function createApp(rootComponent) {
5 | return {
6 | mount(rootContainer) {
7 | const vnode = createVnode(rootComponent);
8 | render(vnode, rootContainer);
9 | },
10 | };
11 | }
12 |
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/runtime-core/h.ts:
--------------------------------------------------------------------------------
1 | import { createVnode } from "./vnode";
2 | export function h(type,props?,children?){
3 | return createVnode(type,props,children)
4 | }
--------------------------------------------------------------------------------
/src/runtime-core/helpers/renderSlots.ts:
--------------------------------------------------------------------------------
1 | import { createVnode, Fragment } from "../vnode";
2 |
3 | export function renderSlots(slots,name,props){
4 | const slot = slots[name];
5 | if(slot){
6 | if(typeof slot === 'function'){
7 | return createVnode(Fragment, {}, slot(props));
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/src/runtime-core/index.ts:
--------------------------------------------------------------------------------
1 | export {h} from './h';
2 | export {renderSlots} from './helpers/renderSlots'
3 | export { createTextVnode } from './vnode';
4 | export { getCurrentInstance} from './component';
5 | export { provide, inject } from "./apiInject";
6 | export {createRenderer} from './renderer';
7 | export {nextTick} from './scheduler';
--------------------------------------------------------------------------------
/src/runtime-core/renderer.ts:
--------------------------------------------------------------------------------
1 | import { effect } from '../reactivity/effect';
2 | import { ShapeFlags } from '../shared/shapeFlags';
3 | import { createComponentInstance, setupComponent } from "./component";
4 | import { shouldUpdateComponent } from './componentUpdateUtils';
5 | import { createAppAPI } from './createApp';
6 | import { queueJobs } from './scheduler';
7 | import { Fragment,Text } from './vnode';
8 |
9 | export function createRenderer(options){
10 | const {
11 | createElement: hostCreateElement,
12 | patchProp: hostPatchProp,
13 | insert: hostInsert,
14 | remove: hostRemove,
15 | setElementText:hostSetElementText,
16 | } = options;
17 | function render(vnode, container, parentComponent) {
18 | patch(null, vnode, container, parentComponent, null);
19 | }
20 | //n1 old n2 new
21 | function patch(n1, n2, container, parentComponent, anchor) {
22 | const { type, shapeFlag } = n2;
23 | switch (type) {
24 | case Fragment:
25 | processFragment(n1, n2, container, parentComponent, anchor);
26 | break;
27 | case Text:
28 | processText(n1, n2, container);
29 | break;
30 | default:
31 | if (shapeFlag & ShapeFlags.ELEMENT) {
32 | processElement(n1, n2, container, parentComponent, anchor);
33 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
34 | processComponent(n1, n2, container, parentComponent, anchor);
35 | }
36 | break;
37 | }
38 | }
39 | function processElement(n1, n2, container, parentComponent, anchor) {
40 | if (!n1) {
41 | mountElement(n1, n2, container, parentComponent, anchor);
42 | } else {
43 | patchElement(n1, n2, container, parentComponent, anchor);
44 | }
45 | }
46 | function patchElement(n1, n2, container, parentComponent, anchor) {
47 | console.log("patchComponent");
48 | console.log("n1", n1);
49 | console.log("n2", n2);
50 | const oldProps = n1.props || EMPTY_OBJ;
51 | const newProps = n2.props || EMPTY_OBJ;
52 | const el = (n2.el = n1.el);
53 | patchChildren(n1, n2, el, parentComponent, anchor);
54 | patchProps(el, oldProps, newProps);
55 | }
56 | function patchChildren(n1, n2, container, parentComponent, anchor) {
57 | const prevShapeFlag = n1.shapeFlag;
58 | const { shapeFlag } = n2;
59 | const c1 = n1.children;
60 | const c2 = n2.children;
61 |
62 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
63 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
64 | //1 把n1都清空
65 | unmountChildren(n1.children);
66 | }
67 | //2 设置text
68 | if (c1 !== c2) {
69 | hostSetElementText(container, c2);
70 | }
71 | } else {
72 | //text to array
73 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
74 | hostSetElementText(container, "");
75 | mountChildren(c2, container, parentComponent, anchor);
76 | } else {
77 | //array
78 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
79 | }
80 | }
81 | }
82 |
83 | function patchKeyedChildren(c1, c2, container, parentComponent,parentAnchor) {
84 | let i = 0;
85 | const l2 = c2.length;
86 | let e1 = c1.length-1;
87 | let e2 = l2 - 1;
88 | function isSomeVNodeType(n1,n2){
89 | return n1.type === n2.type && n1.key === n2.key;
90 | }
91 | //左侧
92 | while(i<=e1 &&i<=e2){
93 | const n1 = c1[i];
94 | const n2 = c2[i];
95 | if(isSomeVNodeType(n1,n2)){
96 | patch(n1, n2, container, parentComponent, parentAnchor);
97 | }else{
98 | break;
99 | }
100 | i++;
101 | }
102 | //右侧
103 | while (i <= e1 && i <= e2) {
104 | const n1 = c1[e1];
105 | const n2 = c2[e2];
106 | if (isSomeVNodeType(n1, n2)) {
107 | patch(n1, n2, container, parentComponent, parentAnchor);
108 | } else {
109 | break;
110 | }
111 |
112 | e1--;
113 | e2--;
114 | }
115 | //新的比老的多 创建
116 | if(i > e1){
117 | if(i <= e2){
118 | const nextPos = e2 + 1;
119 | const anchor = e2 + 1 < l2 ? c2[nextPos].el : null;
120 | while(i<=e2){
121 | patch(null, c2[i], container, parentComponent, anchor);
122 | i++;
123 | }
124 | }
125 | }else if(i > e2){
126 | while(i <= e1){
127 | hostRemove(c1[i].el);
128 | i++;
129 | }
130 | }else{
131 | let s1 = i;
132 | let s2 = i;
133 |
134 | const toBePatched = e2 - s2 + 1;
135 | let patched = 0;
136 |
137 | const keyToNewIndexMap = new Map();
138 | const newIndexToOldIndexMap = new Array(toBePatched);
139 | let moved = false;
140 | let maxNewIndexSoFar = 0;
141 | for (let i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;
142 |
143 | for(let i = s2;i<=e2;i++){
144 | const nextChild = c2[i];
145 | keyToNewIndexMap.set(nextChild.key,i);
146 | }
147 | for(let i = s1;i<=e1;i++){
148 | const prevChild = c1[i];
149 | if(patched>=toBePatched){
150 | hostRemove(prevChild.el);
151 | continue;
152 | }
153 | let newIndex;
154 | if(prevChild.key!=null){
155 | newIndex = keyToNewIndexMap.get(prevChild.key);
156 | }else{
157 | for(let j = s2;j<=e2;j++){
158 | if(isSomeVNodeType(prevChild,c2[j])){
159 | newIndex = j;
160 | break;
161 | }
162 |
163 | }
164 | }
165 | if(newIndex===undefined){
166 | hostRemove(prevChild.el);
167 | }else{
168 | if(newIndex>=maxNewIndexSoFar){
169 | maxNewIndexSoFar = newIndex
170 | }else{
171 | moved = true;
172 | }
173 | newIndexToOldIndexMap[newIndex-s2] = i+1;
174 | patch(prevChild,c2[newIndex],container,parentComponent,null);
175 | patched++;
176 | }
177 |
178 | }
179 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap):[];
180 | let j = increasingNewIndexSequence.length-1;
181 | for(let i = toBePatched-1;i>=0;i--){
182 | const nextIndex = i + s2;
183 | const nextChild = c2[nextIndex];
184 | const anchor = nextIndex+1> 1;
219 | if (arr[result[c]] < arrI) {
220 | u = c + 1;
221 | } else {
222 | v = c;
223 | }
224 | }
225 | if (arrI < arr[result[u]]) {
226 | if (u > 0) {
227 | p[i] = result[u - 1];
228 | }
229 | result[u] = i;
230 | }
231 | }
232 | }
233 | u = result.length;
234 | v = result[u - 1];
235 | while (u-- > 0) {
236 | result[u] = v;
237 | v = p[v];
238 | }
239 | return result;
240 | }
241 |
242 | function unmountChildren(children){
243 | for(let i = 0;i {
295 | patch(null, v, container, parentComponent, anchor);
296 | });
297 | }
298 | function processComponent(n1, n2, container, parentComponent, anchor) {
299 | if(!n1){
300 | mountComponent(n2, container, parentComponent, anchor);
301 | }else{
302 | updateComponent(n1,n2);
303 | }
304 | }
305 |
306 | function updateComponent(n1,n2){
307 | const instance = (n2.component = n1.component);
308 | if(shouldUpdateComponent(n1,n2)){
309 | instance.next = n2;
310 | instance.update();
311 | }else{
312 | n2.component = n1.component;
313 | n2.el = n1.el;
314 | instance.vnode = n2;
315 | }
316 |
317 | }
318 |
319 | function mountComponent(initialVnode, container, parentComponent, anchor) {
320 | const instance = (initialVnode.component = createComponentInstance(
321 | initialVnode,
322 | parentComponent
323 | ));
324 | setupComponent(instance);
325 | setupRenderEffect(instance, initialVnode, container, anchor);
326 | }
327 | function setupRenderEffect(instance, initialVnode, container, anchor) {
328 | instance.update = effect(() => {
329 | if (!instance.isMounted) {
330 | console.log("init");
331 | const { proxy } = instance;
332 | const subTree = (instance.subTree = instance.render.call(proxy));
333 | patch(null, subTree, container, instance, anchor);
334 | initialVnode.el = subTree.el;
335 | instance.isMounted = true;
336 | } else {
337 | console.log("update");
338 | const {next,vnode} = instance;
339 |
340 | if(next){
341 | next.el = vnode.el;
342 | updateComponentPreRender(instance,next);
343 | }
344 |
345 | const { proxy } = instance;
346 | const subTree = instance.render.call(proxy);
347 | const prevSubTree = instance.subTree;
348 | instance.subTree = subTree;
349 | patch(prevSubTree, subTree, container, instance, anchor);
350 | }
351 | },{
352 | scheduler(){
353 | console.log('update--scheduler')
354 | queueJobs(instance.update);
355 | }
356 | });
357 | }
358 | return {
359 | createApp:createAppAPI(render)
360 | }
361 | }
362 |
363 | function updateComponentPreRender(instance, nextVnode) {
364 | instance.vnode = nextVnode;
365 | instance.next = null;
366 | instance.props = nextVnode.props;
367 | }
368 |
369 |
--------------------------------------------------------------------------------
/src/runtime-core/scheduler.ts:
--------------------------------------------------------------------------------
1 | const queue:any[] = [];
2 | let isFlushPending = false;
3 | const p = Promise.resolve();
4 | export function nextTick(fn){
5 | return fn ? p.then(fn):p;
6 | }
7 |
8 | export function queueJobs(job){
9 | if(!queue.includes(job)){
10 | queue.push(job)
11 | }
12 | queueFlush();
13 | }
14 | function queueFlush(){
15 | if (isFlushPending) return;
16 | isFlushPending = true;
17 | nextTick(flushJobs);
18 | }
19 | function flushJobs(){
20 | isFlushPending = false;
21 | let job;
22 | while ((job = queue.shift())) {
23 | job && job();
24 | }
25 | }
--------------------------------------------------------------------------------
/src/runtime-core/vnode.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/shapeFlags";
2 |
3 | export const Fragment = Symbol("Fragment");
4 | export const Text = Symbol("Text");
5 | export function createVnode(type,props?,children?){
6 | const vnode = {
7 | type,
8 | props,
9 | children,
10 | el:null,
11 | key:props && props.key,
12 | component:null,
13 | shapeFlag:getShapeFlag(type)
14 | }
15 | if(typeof children ==='string'){
16 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN
17 | }else if(Array.isArray(children)){
18 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN;
19 | }
20 | if(vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT){
21 | if(typeof children === 'object'){
22 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN;
23 | }
24 | }
25 | return vnode;
26 | }
27 | export function createTextVnode(text:string){
28 | return createVnode(Text,{},text)
29 | }
30 | function getShapeFlag(type){
31 | return typeof type === "string" ? ShapeFlags.ELEMENT : ShapeFlags.STATEFUL_COMPONENT
32 | }
--------------------------------------------------------------------------------
/src/runtime-dom/index.ts:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "../runtime-core";
2 | function createElement(type) {
3 | return document.createElement(type);
4 | }
5 | function patchProp(el, key, oldVal,newVal) {
6 | const isOn = (key: string) => /^on[A-Z]/.test(key);
7 | if (isOn(key)) {
8 | const event = key.slice(2).toLowerCase();
9 | el.addEventListener(event, newVal);
10 | } else {
11 | if(newVal === undefined || newVal === null){
12 | el.removeAttribute(key, newVal);
13 | }else{
14 | el.setAttribute(key, newVal);
15 | }
16 | }
17 | }
18 | function insert(child, parent,anchor) {
19 | parent.insertBefore(child,anchor || null)
20 | }
21 | function remove(child){
22 | const parent = child.parentNode;
23 | if(parent){
24 | parent.removeChild(child);
25 | }
26 | }
27 | function setElementText(container,text){
28 | container.textContent = text;
29 | }
30 | const render: any = createRenderer({
31 | createElement,
32 | patchProp,
33 | insert,
34 | remove,
35 | setElementText
36 | });
37 | export function createApp(...args){
38 | return render.createApp(...args)
39 | }
40 | export * from "../runtime-core";
--------------------------------------------------------------------------------
/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | export const extend = Object.assign;
2 | export const isObject = (val) => {
3 | return val !== null && typeof val === "object";
4 | };
5 | export const hasChanged = (newValue,value) => {
6 | return !Object.is(newValue,value);
7 | };
8 | export const camelize = (str: string) => {
9 | return str.replace(/-(\w)/g, (_, c: string) => {
10 | return c ? c.toUpperCase() : "";
11 | });
12 | };
13 | const capitalize = (str: string) => {
14 | return str.charAt(0).toUpperCase() + str.slice(1);
15 | };
16 | export const toHandlerKey = (str: string) => {
17 | return str ? "on" + capitalize(str) : "";
18 | };
--------------------------------------------------------------------------------
/src/shared/shapeFlags.ts:
--------------------------------------------------------------------------------
1 | export const enum ShapeFlags {
2 | ELEMENT = 1,
3 | STATEFUL_COMPONENT = 1 << 1,
4 | TEXT_CHILDREN = 1 << 2,
5 | ARRAY_CHILDREN = 1 << 3,
6 | SLOT_CHILDREN = 1 << 4,
7 | }
--------------------------------------------------------------------------------
/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": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | "lib": ["DOM","es6","ES2016"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 |
26 | /* Modules */
27 | "module": "esnext", /* Specify what module code is generated. */
28 | // "rootDir": "./", /* Specify the root folder within your source files. */
29 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
34 | "types": ["jest"], /* Specify type package names to be included without being referenced in a source file. */
35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
36 | // "resolveJsonModule": true, /* Enable importing .json files */
37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
38 |
39 | /* JavaScript Support */
40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
43 |
44 | /* Emit */
45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
49 | // "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. */
50 | // "outDir": "./", /* Specify an output folder for all emitted files. */
51 | // "removeComments": true, /* Disable emitting comments. */
52 | // "noEmit": true, /* Disable emitting files from a compilation. */
53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
61 | // "newLine": "crlf", /* Set the newline character for emitting files. */
62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
68 |
69 | /* Interop Constraints */
70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
75 |
76 | /* Type Checking */
77 | "strict": true, /* Enable all strict type-checking options. */
78 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
96 |
97 | /* Completeness */
98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
100 | }
101 | }
102 |
--------------------------------------------------------------------------------