├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── LICENSE
├── README.md
├── demo
├── demo1
│ ├── package.json
│ ├── src
│ │ ├── app.jsx
│ │ └── index.html
│ └── webpack.config.js
└── demo2
│ ├── .gitignore
│ ├── main.js
│ ├── package.json
│ ├── public
│ └── index.html
│ ├── src
│ ├── App.jsx
│ ├── assets
│ │ └── main.gif
│ ├── components
│ │ └── HelloWorld.jsx
│ └── style
│ │ └── App.css
│ └── webpack.config.js
├── docs
├── README.md
└── static
│ ├── QQ录屏20220823174152.mp4
│ ├── component.gif
│ ├── props.gif
│ ├── 双向绑定.gif
│ └── 基本示例.gif
├── examples
├── app.js
├── boundle
│ ├── boundle.js
│ └── boundle.js.map
└── index.html
├── index.d.ts
├── index.ts
├── jest.config.js
├── lib
├── boundle.js
├── boundle.js.map
├── boundle.umd.js
├── boundle.umd.js.map
├── bundle.esm.js
└── bundle.esm.js.map
├── package.json
├── rollup.config.js
├── src
├── __test__
│ └── index.ts
├── app.ts
├── component.ts
├── index.ts
├── lifeCycle.ts
├── mount
│ ├── element.ts
│ ├── mount.ts
│ └── patch.ts
├── plugin.ts
├── reactive
│ ├── __test__.ts
│ ├── active.ts
│ ├── index.ts
│ ├── reactive.ts
│ ├── ref.ts
│ ├── update.ts
│ ├── utils.ts
│ └── watch.ts
├── render.ts
├── utils.ts
└── vnode
│ ├── alive.ts
│ ├── array.ts
│ ├── component.ts
│ ├── element.ts
│ ├── fragment.ts
│ ├── index.ts
│ ├── text.ts
│ └── vnode.ts
├── test
└── index.test.ts
└── tsconfig.json
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Actions Demo
2 | run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
3 | on: [push]
4 | jobs:
5 | Explore-GitHub-Actions:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
9 | - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
10 | - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
11 | - name: Check out repository code
12 | uses: actions/checkout@v3
13 | - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
14 | - run: echo "🖥️ The workflow is now ready to test your code on the runner."
15 | - name: List files in the repository
16 | run: |
17 | ls ${{ github.workspace }}
18 | - run: echo "🍏 This job's status is ${{ job.status }}."
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # user
2 | .DS_Store
3 | node_modules
4 | package-lock.json
5 | yarn.lock
6 |
7 | # local env files
8 | .env.local
9 | .env.*.local
10 |
11 | # Log files
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 | pnpm-debug.log*
16 |
17 | # Editor directories and files
18 | .idea
19 | .vscode
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | *.zip
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 stuzhou
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vact
2 |
3 | 借鉴加创新的前端响应式框架
4 |
5 | [](https://www.oscs1024.com/project/yanyunwu/vact?ref=badge_small)
6 |
7 |
8 |
9 | ## 使用文档
10 |
11 | [点击查看vact使用文档](./docs/README.md)
12 |
13 | [备用文档链接](https://github.com/yanyunwu/vact/blob/master/docs/README.md)
14 |
15 |
16 |
17 |
18 | ## 说点什么?
19 |
20 | 首先介绍一下自己,我不是什么大佬,只是有时候会对于一些东西感兴趣,所以才写了这个东西。它可能没有什么特点,而且更是比不过现在已经非常成熟的vue和react,这点我当然是知道的,而且我还是借鉴和使用了它们的一些思想,但假如说有那么一些创新,或者是有趣的东西,那便足够了,不是吗?我只希望能得到大家的建议,并从中学习进步,就足够了。
21 |
22 |
23 |
24 | ## 代码示例
25 |
26 | ```jsx
27 | import { createApp, state } from 'vactapp'
28 |
29 | const $data = {
30 | count: 0,
31 | color: 'red'
32 | }
33 |
34 | let show = state(true)
35 |
36 | const head = <>
37 |
hello world!
38 | $data.color = 'blue'}>
39 | >
40 |
41 | const bottom = <>
42 | 底部显示
43 | >
44 |
45 | const app =
46 | {head}
47 |
48 | 计数器
49 |
50 | {$data.count}
51 |
52 |
53 | {show.value && bottom}
54 |
55 |
56 |
57 | createApp(app).mount('#app')
58 | ```
59 |
60 |
61 |
62 | ## 响应式
63 |
64 | 通过`defineState`和`state`定义响应式对象
65 |
66 | ```jsx
67 | import { defineState, state, createApp } from 'vactapp'
68 |
69 | const data = defineState({
70 | text: 'hello'
71 | })
72 |
73 | const text = state('world!')
74 |
75 | const app = <>{data.text} {text.value}>
76 |
77 | createApp({app}
).mount('#app')
78 | ```
79 |
80 |
81 |
82 | ### babel解析响应式对象
83 |
84 | 你可能会好奇为什么第一个例子的$data对象只是单纯的一个对象
85 |
86 | 事实上,在我写的babel插件中会自动把以$开头的且内容为对象的变量转义为`defineState`
87 |
88 | ```js
89 | const $data = {
90 | count: 0,
91 | color: 'red'
92 | }
93 | // 等同于
94 | const $data = defineState({
95 | count: 0,
96 | color: 'red'
97 | })
98 | ```
99 |
100 |
101 |
102 | ***以上只是简单的示例,详细使用请务必查看文档***
--------------------------------------------------------------------------------
/demo/demo1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo1",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "dev": "webpack serve --open",
8 | "build": "webpack"
9 | },
10 | "devDependencies": {
11 | "@babel/core": "^7.18.9",
12 | "@babel/preset-env": "^7.18.9",
13 | "babel-loader": "^8.2.5",
14 | "babel-plugin-syntax-jsx": "^6.18.0",
15 | "css-loader": "^6.7.1",
16 | "html-webpack-plugin": "^5.5.0",
17 | "style-loader": "^3.3.1",
18 | "webpack": "^5.73.0",
19 | "webpack-cli": "^4.10.0",
20 | "webpack-dev-server": "^4.9.3"
21 | },
22 | "dependencies": {
23 | "babel-plugin-transform-vactapp-jsx": "^0.1.3",
24 | "vactapp": "^0.9.2"
25 | }
26 | }
--------------------------------------------------------------------------------
/demo/demo1/src/app.jsx:
--------------------------------------------------------------------------------
1 | import { createApp, state } from 'vactapp'
2 |
3 | const $data = {
4 | count: 0,
5 | color: 'red'
6 | }
7 |
8 | let show = state(true)
9 |
10 | const head = <>
11 | hello world!
12 | $data.color = 'blue'}>
13 | >
14 |
15 | const bottom = <>
16 | 底部显示
17 | >
18 |
19 | const app =
20 | {head}
21 |
22 | 计数器
23 |
24 | {$data.count}
25 |
26 |
27 | {show.value && bottom}
28 |
29 |
30 |
31 | createApp(app).mount('#app')
--------------------------------------------------------------------------------
/demo/demo1/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/demo/demo1/webpack.config.js:
--------------------------------------------------------------------------------
1 | // 因为 webpack 是基于 node
2 | // 所以在配置文件里面 我们可以直接使用 node 的语法
3 | const path = require('path')
4 | const htmlWebpackPlugin = require('html-webpack-plugin')
5 |
6 | module.exports = {
7 | mode: 'development',
8 | // 入口
9 | entry: path.join(__dirname, './src/app.jsx'),
10 |
11 | // 出口
12 | output: {
13 | path: path.join(__dirname, './dist'),
14 | filename: 'bundle.js',
15 | },
16 |
17 | module: {
18 | rules: [
19 | {
20 | test: /\.css$/, //正则表达式,匹配文件类型
21 | use: ["style-loader", "css-loader"] //申明使用什么loader进行处理
22 | },
23 | {
24 | test: /\.(js|jsx)$/,
25 | exclude: /(node_modules)/,
26 | use: {
27 | loader: 'babel-loader',
28 | options: {
29 | presets: ['@babel/preset-env'],
30 | plugins: [
31 | "syntax-jsx",
32 | "transform-vactapp-jsx"
33 | ]
34 | }
35 | }
36 | },
37 | ]
38 | },
39 |
40 | plugins: [
41 | // 使用插件 指定模板
42 | new htmlWebpackPlugin({
43 | template: path.join(__dirname, './src/index.html')
44 | })
45 | ],
46 |
47 | devServer: {
48 | open: true,
49 | port: 8080
50 | }
51 | }
--------------------------------------------------------------------------------
/demo/demo2/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | package-lock.json
4 | yarn.lock
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 | *.zip
25 |
--------------------------------------------------------------------------------
/demo/demo2/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vactapp'
2 | import App from './src/App'
3 | import './src/style/App.css'
4 |
5 | createApp(App).mount('#app')
--------------------------------------------------------------------------------
/demo/demo2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo1",
3 | "version": "1.0.0",
4 | "main": "main.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "dev": "webpack serve --open",
8 | "build": "webpack"
9 | },
10 | "devDependencies": {
11 | "@babel/core": "^7.18.9",
12 | "@babel/preset-env": "^7.18.9",
13 | "babel-loader": "^8.2.5",
14 | "babel-plugin-syntax-jsx": "^6.18.0",
15 | "css-loader": "^6.7.1",
16 | "file-loader": "^6.2.0",
17 | "html-webpack-plugin": "^5.5.0",
18 | "style-loader": "^3.3.1",
19 | "url-loader": "^4.1.1",
20 | "webpack": "^5.73.0",
21 | "webpack-cli": "^4.10.0",
22 | "webpack-dev-server": "^4.9.3"
23 | },
24 | "dependencies": {
25 | "babel-plugin-transform-vactapp-jsx": "^0.1.3",
26 | "vactapp": "^0.9.2"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/demo/demo2/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/demo/demo2/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { HelloWorld } from './components/HelloWorld'
2 | import { state } from 'vactapp'
3 |
4 | function App() {
5 | const count = state(0)
6 | return
7 |
8 |
9 |
10 |
11 |
12 |
13 | }
14 |
15 | export default
--------------------------------------------------------------------------------
/demo/demo2/src/assets/main.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanyunwu/vact/6e07d0ac4ab15023ca7c032c7ebd95eb8937383d/demo/demo2/src/assets/main.gif
--------------------------------------------------------------------------------
/demo/demo2/src/components/HelloWorld.jsx:
--------------------------------------------------------------------------------
1 |
2 | export class HelloWorld {
3 | render() {
4 | return
5 |
Hello World!
6 |
7 | }
8 | }
--------------------------------------------------------------------------------
/demo/demo2/src/style/App.css:
--------------------------------------------------------------------------------
1 |
2 | .app-img {
3 | text-align: center;
4 | }
5 |
6 | .app-img img{
7 | height: 200px;
8 | width: 200px;
9 | border-radius: 5px;
10 | }
11 |
12 | .btn {
13 | margin: 10px;
14 | width: 100px;
15 | height: 40px;
16 | border: 1px solid transparent;
17 | border-radius: 10px;
18 | font-size: 15px;
19 | transition: all 0.3s;
20 | }
21 |
22 | .btn:hover {
23 | border: 1px solid skyblue
24 | }
--------------------------------------------------------------------------------
/demo/demo2/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const htmlWebpackPlugin = require('html-webpack-plugin')
3 |
4 | module.exports = {
5 | mode: 'development',
6 | entry: path.join(__dirname, './main.js'),
7 | output: {
8 | path: path.join(__dirname, './dist'),
9 | filename: 'bundle.js',
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.css$/,
15 | use: ["style-loader", "css-loader"]
16 | },
17 | {
18 | test: /\.(js|jsx)$/,
19 | exclude: /(node_modules)/,
20 | use: {
21 | loader: 'babel-loader',
22 | options: {
23 | presets: ['@babel/preset-env'],
24 | plugins: ["syntax-jsx", "transform-vactapp-jsx"]
25 | }
26 | }
27 | },
28 | {
29 | test: /\.(png|jpg|gif|jpeg)$/,
30 | exclude: /node_modules/,
31 | type: 'javascript/auto',
32 | use: [
33 | {
34 | loader: 'url-loader',
35 | options: {
36 | limit: 10240,
37 | esModule: false,
38 | name: 'assets/[name].[ext]'
39 | }
40 | }
41 | ]
42 | }
43 | ]
44 | },
45 | plugins: [
46 | new htmlWebpackPlugin({ template: path.join(__dirname, './public/index.html') })
47 | ],
48 | devServer: {
49 | open: true,
50 | port: 8080
51 | },
52 | resolve: {
53 | extensions: ['.js', '.jsx']
54 | }
55 | }
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Vact使用文档
2 |
3 | (以下tode是已经实现的内容,文档还未写)
4 |
5 |
6 | - [Vact使用文档](#vact使用文档)
7 | - [简介](#简介)
8 | - [整体架构思想](#整体架构思想)
9 | - [基本示例](#基本示例)
10 | - [渐进式](#渐进式)
11 | - [快速使用](#快速使用)
12 | - [配合webpack使用](#配合webpack使用)
13 | - [babel配置](#babel配置)
14 | - [直接使用demo环境](#直接使用demo环境)
15 | - [基础](#基础)
16 | - [创建应用](#创建应用)
17 | - [jsx语法](#jsx语法)
18 | - [响应式](#响应式)
19 | - [css样式绑定](#css样式绑定)
20 | - [元素节点响应绑定](#元素节点响应绑定)
21 | - [条件渲染](#条件渲染)
22 | - [条件渲染封装](#条件渲染封装)
23 | - [列表渲染](#列表渲染)
24 | - [事件绑定](#事件绑定)
25 | - [表单绑定](#表单绑定)
26 | - [监听器](#监听器)
27 | - [组件使用](#组件使用)
28 | - [声明并使用组件](#声明并使用组件)
29 | - [函数组件](#函数组件)
30 | - [类组件](#类组件)
31 | - [组件属性](#组件属性)
32 | - [组件插槽](#组件插槽)
33 | - [插槽](#插槽)
34 | - [具名插槽](#具名插槽)
35 | - [动态组件](#动态组件)
36 | - [结尾](#结尾)
37 | - [feture](#feture)
38 |
39 | ## 简介
40 |
41 | **vact**,从名字就能看出,和**vue**还有**react**是少不了关系的,它是一款基于**vue**的响应式原理,以及**react**的**jsx**语法,并结合我自身的创新开发而来。当然,仅凭我一个人是无法将它维护的很好的,所以,我想借助它来向大家展示一下我自己的想法以及一些有趣的用法,那么接下来,请听我娓娓道来。
42 |
43 |
44 |
45 | ### 整体架构思想
46 |
47 | 这套框架的当然是具备声明式渲染和响应性的特点的,但不同的是,它并不是以比较diff为主,而是在一开始就确认好每一个活跃的dom节点的响应性,只有在一些比较严苛的条件(比如数组),才会启用diff进行比较。如果你不理解的话可以先接着往下看,这不影响你接下来的阅读。
48 |
49 |
50 |
51 | ### 基本示例
52 |
53 | ***注:下面的实例都是基于jsx语法,因为使用创建节点的api过于繁琐***
54 |
55 | ```jsx
56 | import { state, createApp } from 'vactapp'
57 |
58 | const count = state(0)
59 | let app =
60 |
63 |
64 |
65 | createApp(app).mount('#app')
66 | ```
67 |
68 | **结果如下:**
69 |
70 | 
71 |
72 |
73 |
74 | ## 渐进式
75 |
76 | 可以通过将节点挂载到真实dom来实现部分响应式代理
77 |
78 |
79 |
80 | ## 快速使用
81 |
82 | 如果使用创建节点的api去写的话会比较麻烦,目前推荐使用jsx并安装我写的babel解析插件
83 |
84 | **|推荐直接在demo中运行!!!|(看下面)**
85 |
86 |
87 |
88 | ### 配合webpack使用
89 |
90 |
91 |
92 | #### babel配置
93 |
94 | **babel预设:**
95 |
96 | `@babel/preset-env`
97 |
98 | **babel插件:**
99 |
100 | `babel-plugin-syntax-jsx `(**识别jsx语法**)
101 |
102 | `babel-plugin-transform-vact-jsx`(**翻译vact的babel**)
103 |
104 |
105 |
106 | #### 直接使用demo环境
107 |
108 | - master分支中demo文件中的demo1目录是简单的脚手架环境
109 | - master分支中demo文件中的demo2目录是完善的webapck脚手架环境,可以解析包括其他各类资源
110 |
111 | **运行demo:**`npm i yarn -g (如果有yarn请跳过)` `yarn install` `yarn dev`
112 |
113 |
114 |
115 | ## 基础
116 |
117 |
118 |
119 | ### 创建应用
120 |
121 | ```jsx
122 | import { createApp } from 'vactapp'
123 |
124 | const app = createApp(Hello World!
)
125 | app.mount('#app')
126 | ```
127 |
128 |
129 |
130 | ### jsx语法
131 |
132 | 这部分其实不需要我过多介绍,现在非常流行,而且网上也有教程
133 |
134 | 简单写一下
135 |
136 | - html代码块中大括号里面用来写表达式
137 |
138 | ```jsx
139 | const template =
140 | {'hello'}
141 |
142 | ```
143 |
144 |
145 |
146 | - 事件以on开头,且代码块中为函数且只能为函数
147 |
148 | ```jsx
149 | const button =
150 | ```
151 |
152 |
153 |
154 | ### 响应式
155 |
156 | 想要实现响应式,必须首先创建响应式对象
157 |
158 | ```jsx
159 | import { defineState, watch } from 'vactapp'
160 |
161 | const data = defineState({
162 | count: 0
163 | })
164 |
165 | watch(() => data.count, (oldValue, newValue) => {
166 | console.log(oldValue, newValue)
167 | })
168 |
169 | data.count++ // 打印 0, 1
170 | ```
171 |
172 |
173 |
174 | 当然也可以创建基础属性的响应对象,但数值必须通过value访问
175 |
176 | ```jsx
177 | import { state } from 'vactapp'
178 |
179 | const count = state(0)
180 |
181 | watch(() => count.value, (oldValue, newValue) => {
182 | console.log(oldValue, newValue)
183 | })
184 |
185 | count.value++ // 打印 0, 1
186 | ```
187 |
188 |
189 |
190 | ### css样式绑定
191 |
192 | style可以为字符串或者对象,如果为对象则prop为属性,value为值,className目前只能写字符串,但两者都可为响应式
193 |
194 | ```jsx
195 |
196 | const color = state('red')
197 | const app = createApp(
198 |
199 | Hello World!
200 |
201 | ).mount('#app')
202 | // or const app = createApp( Hello World!
)
203 | setTimeout(() => color.value = 'blue', 3000)
204 |
205 | ```
206 |
207 | className的响应同style的第二个
208 |
209 | **不仅是style和className,任何引用响应式变量的属性都会变为响应式**
210 |
211 |
212 |
213 | ### 元素节点响应绑定
214 |
215 | 子元素在代码块中且引用响应式变量即可实现响应式
216 |
217 | ```jsx
218 | const show = state(true)
219 | const app = createApp(
220 |
221 | {show.value && 'Hello World!'}
222 |
223 | ).mount('#app')
224 |
225 | setInterval(() => show.value = !show.value, 1000)
226 | ```
227 |
228 |
229 |
230 | ### 条件渲染
231 |
232 | 上一个例子中我们展示了单对象条件渲染
233 |
234 | 我们再展示一个**双对象条件渲染**
235 |
236 | ```jsx
237 | const show = state(true)
238 | const app = createApp(
239 |
240 | {show.value ? Hello : World }
241 |
242 | ).mount('#app')
243 |
244 | setInterval(() => show.value = !show.value, 1000)
245 | ```
246 |
247 | **多对象条件渲染**
248 |
249 | ```jsx
250 | function App() {
251 | let data = defineState({
252 | show: 1,
253 | })
254 |
255 | let item = () => {
256 | if (data.show === 1) {
257 | return 条件1
258 | } else if (data.show === 2) {
259 | return 条件2
260 | } else {
261 | return null
262 | }
263 | }
264 |
265 | return
266 | {item()}
267 |
268 |
269 | }
270 |
271 | createApp().mount('#app')
272 | ```
273 |
274 |
275 |
276 | #### 条件渲染封装
277 |
278 | 如果你已经看完了组件部分,你甚至可以封装一个**v-if**
279 |
280 | ```jsx
281 | class HelloWorld{
282 | render() {
283 | return () => this.props['v-if'] ? this.component() : null
284 | }
285 |
286 | component() {
287 | return hhhh
288 | }
289 | }
290 |
291 | let show = state(true)
292 | function App() {
293 | return
294 |
295 |
296 |
297 | }
298 | createApp().mount('#app')
299 | ```
300 |
301 | 你甚至可以写一个根组件,用于给任意组件提供这个能力
302 |
303 | ```jsx
304 | // 封装一个提供v-if能力的组件
305 | class VIF {
306 | render() {
307 | return () => this.props['v-if'] ? this.component() : null
308 | }
309 | }
310 |
311 | class HelloWorld extends VIF {
312 | component() {
313 | return hhhh
314 | }
315 | }
316 |
317 | let show = state(true)
318 | function App() {
319 | return
320 |
321 |
322 |
323 | }
324 | createApp().mount('#app')
325 | ```
326 |
327 | 怎么样,抛开性能不谈,是不是很有趣,有时候我们可以为了有趣去学习,而不是烦恼患得患失
328 |
329 |
330 |
331 | ### 列表渲染
332 |
333 | 如果我们将一个数组返回到代码块,那么它将会把这个数组整体作为一个数组节点渲染到页面
334 |
335 | ```jsx
336 | function App() {
337 | const data = defineState({
338 | list: [1, 2, 3, 4, 5]
339 | })
340 |
341 |
342 | return
343 |
344 | {data.list.map(num => - {num}
)}
345 |
346 |
347 |
348 |
349 | }
350 | ```
351 |
352 | 目前对于数组节点的渲染更新默认是没有优化的,不过你可以在配置项开启优化,该优化会启用diff算法进行比较更新,复用节点,目前还在实验,可能会有bug
353 |
354 | ```jsx
355 | createApp(, { arrayDiff: true }).mount('#app')
356 | ```
357 |
358 |
359 |
360 | ### 事件绑定
361 |
362 | 事件都是以on开头,传入一个函数即可,但是事件可以作为props传入组件内部使用
363 |
364 | ```jsx
365 | function Input(props) {
366 | return
367 | props.onChange(e.target.value)} />
368 |
369 | }
370 |
371 | function App() {
372 | const text = state('')
373 | return
374 | {text.value}
375 | text.value = newText} />
376 |
377 | }
378 |
379 | createApp().mount('#app')
380 | ```
381 |
382 | 
383 |
384 | 怎么样,是不是很熟悉,没错就是v-model双向绑定,而且非常简单灵活,有趣吧
385 |
386 |
387 |
388 | ### 表单绑定
389 |
390 | 上面那个例子就生动形象的体现了表单绑定,这里我就不再举例了
391 |
392 |
393 |
394 | ### 监听器
395 |
396 | 我们可以监听任意个响应式变量来设置一个回调函数
397 |
398 | **监听一个**
399 |
400 | ```js
401 | const data = defineState({
402 | count: 0
403 | })
404 |
405 | watch(() => data.count, (oldValue, newValue) => {
406 | console.log(oldValue, newValue)
407 | })
408 |
409 | data.count++
410 | ```
411 |
412 | **监听多个**
413 |
414 | ```js
415 | const data = defineState({
416 | count: 0,
417 | count2: 1
418 | })
419 |
420 | watch(() => data.count + data.count2, (oldValue, newValue) => {
421 | // 注意这里返回的是两者相加的值,而不是单单一个变量
422 | console.log(oldValue, newValue)
423 | })
424 |
425 | data.count++
426 | data.count2++
427 | ```
428 |
429 | 你可以通过这个在响应式变量变化后做一些额外的操作
430 |
431 |
432 |
433 | ### 组件使用
434 |
435 | #### 声明并使用组件
436 |
437 | 你可以通过函数声明一个组件,如果是作为函数调用,会传入props和children,如果是作为构造函数,原型必须有一个render函数,props和children会绑定在this上
438 |
439 |
440 |
441 | ##### 函数组件
442 |
443 | ```jsx
444 | function Button(props, children) {
445 | return <>>
446 | }
447 | const text = state('哈哈哈')
448 | const app =
449 |
450 | createApp(app).mount('#app')
451 | ```
452 |
453 |
454 |
455 | ##### 类组件
456 |
457 | 必须有一个render函数, 返回一个根节点(可以为fragment)
458 |
459 | ```jsx
460 | class App {
461 | constructor() {
462 | this.data = defineState({
463 | count: 0
464 | })
465 | }
466 |
467 | render() {
468 | console.log(this.props, this.children)
469 | return
470 | {this.data.count}
471 |
472 | this.data.count++} >增加
473 |
474 | }
475 | }
476 | ```
477 |
478 | 你可以使用函数声明组件,返回需要渲染的html,同时属性和子元素会作为参数传入
479 |
480 |
481 |
482 | #### 组件属性
483 |
484 | 组件属性如果是响应式的,那么它的响应性会传递到子组件,且你可以将props的值无限向下传递,仍然会保持它的响应
485 |
486 | ```jsx
487 | function Fun1(props) {
488 | return
489 | fun1-value: {props.value}
490 |
491 |
492 | }
493 |
494 | function Fun2(props) {
495 | return <>
496 | fun2-value: {props.value}
497 | >
498 | }
499 |
500 | function App() {
501 | const value = state(0)
502 |
503 | return
504 |
505 |
506 |
507 | }
508 | ```
509 |
510 |
511 |
512 | 
513 |
514 | #### 组件插槽
515 |
516 | ##### 插槽
517 |
518 | 插槽将作为children传入
519 |
520 | ```jsx
521 | function Text(props, children) {
522 | return <>{children}>
523 | }
524 | const show = state(true)
525 | const app =
526 |
527 | {show.value && hello}
528 | world
529 |
530 |
531 |
532 | setInterval(() => {
533 | show.value = !show.value
534 | }, 1000);
535 |
536 | createApp(app).mount('#app')
537 | ```
538 |
539 |
540 |
541 | ##### 具名插槽
542 |
543 | 这边不太建议使用children来实现具名插槽,也不是不行
544 |
545 | 更推荐使用props实现
546 |
547 | ```jsx
548 | function Text(props, children) {
549 | const slots = props.slots
550 | return <>
551 | **{slots.head}**
552 | **{slots.middle}**
553 | **{slots.bottom}**
554 | >
555 | }
556 | const $slots = {
557 | head: 头部,
558 | middle: 中部,
559 | bottom: 底部
560 | }
561 | const app =
562 |
563 |
564 |
567 |
568 |
569 |
570 | createApp(app).mount('#app')
571 | ```
572 |
573 |
574 |
575 | #### 动态组件
576 |
577 | 在条件渲染中我们已经展示过了动态组件,简单来说,动态组件返回的是一个函数,在这个函数中我们需要引用响应式变量并返回html模板
578 |
579 | ```jsx
580 | function Fun(props) {
581 | return function () {
582 | if (props.isHello) {
583 | return hello
584 | } else {
585 | return world
586 | }
587 | }
588 | }
589 |
590 |
591 |
592 | function App() {
593 | const isHello = state(true)
594 |
595 | return
596 |
597 |
598 |
599 | }
600 | ```
601 |
602 | 让我们看看,Fun返回了一个函数,然后我们说过,props可以保存变量的响应性,所以我们就得到了一个动态组件
603 |
604 |
605 |
606 | 我们甚至可以封装一下动态组件,就像vue中的component组件一样
607 |
608 | ```jsx
609 | function Component(props) {
610 | const components = props.components
611 | return function () {
612 | let Cur = components[props.is]
613 | if (Cur) return
614 | else return null
615 | }
616 | }
617 |
618 | const components = {
619 | one: () => one
,
620 | two: () => two
,
621 | three: () => three
622 | }
623 |
624 | function App() {
625 | const is = state('one')
626 | return
627 |
628 | {Object.keys(components).map(name => )}
629 |
630 | }
631 | ```
632 |
633 | 
634 |
635 | 怎么样,有意思吧,如果你觉得有趣,不妨写一个demo试试
636 |
637 | ## 结尾
638 |
639 | 如果你有更好的想法或者建议,请随时call我,本人目前大二马上大三,菜比一枚,可以共同进步交流
640 |
641 | *qq:2480721346*
642 |
643 | ### feture
644 |
645 | 如果未来有时间的话,我会完善一下框架的细节,或者写一个router,学习写一个脚手架之类的
--------------------------------------------------------------------------------
/docs/static/QQ录屏20220823174152.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanyunwu/vact/6e07d0ac4ab15023ca7c032c7ebd95eb8937383d/docs/static/QQ录屏20220823174152.mp4
--------------------------------------------------------------------------------
/docs/static/component.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanyunwu/vact/6e07d0ac4ab15023ca7c032c7ebd95eb8937383d/docs/static/component.gif
--------------------------------------------------------------------------------
/docs/static/props.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanyunwu/vact/6e07d0ac4ab15023ca7c032c7ebd95eb8937383d/docs/static/props.gif
--------------------------------------------------------------------------------
/docs/static/双向绑定.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanyunwu/vact/6e07d0ac4ab15023ca7c032c7ebd95eb8937383d/docs/static/双向绑定.gif
--------------------------------------------------------------------------------
/docs/static/基本示例.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanyunwu/vact/6e07d0ac4ab15023ca7c032c7ebd95eb8937383d/docs/static/基本示例.gif
--------------------------------------------------------------------------------
/examples/app.js:
--------------------------------------------------------------------------------
1 | const { mount, render, defineState, Text, Fragment, Component, createVNode, createApp } = Vact
2 |
3 |
4 | /* const state = defineState({
5 | show: true,
6 | show1: true,
7 | list: [1, 2, 3],
8 | text: 1,
9 | color: 'red'
10 | })
11 |
12 | class App extends Component {
13 | constructor() {
14 | super({
15 | data: {
16 | color: 'red'
17 | }
18 | })
19 | }
20 |
21 | render(h) {
22 | console.log(this.props.aaa);
23 | return h('div', { style: () => ({ color: this.props.aaa }), onClick: () => this.data.color = 'yellow' }, this.children)
24 | }
25 | }
26 |
27 | const app = render('div', { onClick: () => state.color = 'blue' }, [
28 | () => state.show ? render(Fragment, null,
29 | render('ul', null, [
30 | () => state.list.map(num => render('li', null, num))
31 | ])
32 | ) : render(App, null, []),
33 | render(Fragment, null, [() => state.text]),
34 | render(App, { aaa: () => state.color, b: 1 }, [
35 | () => state.show1 ? render('span', null, 1) : 2,
36 | render('span', null, 8888)
37 | ]),
38 | render('button', { onClick: () => state.show = !state.show }, '控制显示')
39 | ])
40 |
41 | setInterval(() => {
42 | state.text++
43 | }, 1000) */
44 |
45 | /* const show = defineState({
46 | list: [{ num: 1, notShow: true }, { num: 2 }, { num: 3 }]
47 | })
48 | const app = render('div', null, [
49 | () => show.list.map(item => () => !item.notShow && render('span', null, () => item.num)),
50 | render('button', { onClick: () => show.list[1].notShow = true }, '切换')
51 | ])
52 |
53 |
54 |
55 | createApp(app).mount('#app')*/
56 |
57 | // import { Fragment } from "vactapp";
58 | // import { createVNode } from "vactapp";
59 | // import { defineState, createApp } from 'vactapp';
60 |
61 | const state = defineState({
62 | list: [3, 4, 5, 2, 2, 1, 6]
63 | });
64 |
65 | const app = render('div', null, [
66 | () => state.list.map(num => render('span', null, num)),
67 | render('button', { onClick: () => state.list.push(state.list.length + 1) }, '增加'),
68 | render('button', { onClick: () => state.list = [] }, '增加'),
69 | render('button', { onClick: () => state.list = [1, 2, 3, 4, 2, 6, 5] }, '增加')
70 | ])
71 |
72 |
73 | createApp(app, { arrayDiff: true }).mount('#app');
74 |
--------------------------------------------------------------------------------
/examples/boundle/boundle.js:
--------------------------------------------------------------------------------
1 |
2 | (function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document);
3 | (function (global, factory) {
4 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
5 | typeof define === 'function' && define.amd ? define(['exports'], factory) :
6 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Vact = {}));
7 | })(this, (function (exports) { 'use strict';
8 |
9 | const TextSymbol = Symbol('Text');
10 |
11 | const FragmentSymbol = Symbol('Fragment');
12 |
13 | const VComponentSymbol = Symbol('VComponent');
14 |
15 | const ArraySymbol = Symbol('ArrayNode');
16 |
17 | const AliveSymbol = Symbol('Alive');
18 |
19 | /**
20 | * 虚拟dom节点类型枚举
21 | */
22 | var VNODE_TYPE;
23 | (function (VNODE_TYPE) {
24 | // 普通元素节点类型
25 | VNODE_TYPE[VNODE_TYPE["ELEMENT"] = 0] = "ELEMENT";
26 | // 文本节点类型
27 | VNODE_TYPE[VNODE_TYPE["TEXT"] = 1] = "TEXT";
28 | VNODE_TYPE[VNODE_TYPE["FRAGMENT"] = 2] = "FRAGMENT";
29 | VNODE_TYPE[VNODE_TYPE["COMPONENT"] = 3] = "COMPONENT";
30 | VNODE_TYPE[VNODE_TYPE["ARRAYNODE"] = 4] = "ARRAYNODE";
31 | VNODE_TYPE[VNODE_TYPE["ALIVE"] = 5] = "ALIVE";
32 | })(VNODE_TYPE || (VNODE_TYPE = {}));
33 |
34 | function isString(content) {
35 | return typeof content === 'string';
36 | }
37 | function isFunction(content) {
38 | return typeof content === 'function';
39 | }
40 | function isFragment(content) {
41 | return content === FragmentSymbol;
42 | }
43 | function isArrayNode(content) {
44 | return content === ArraySymbol;
45 | }
46 | function isText(content) {
47 | return content === TextSymbol;
48 | }
49 | function isActiver(content) {
50 | return isObject(content) && content.flag === 'activer';
51 | }
52 | function isVNode(content) {
53 | return isObject(content) && content.flag in VNODE_TYPE;
54 | }
55 | function isObjectExact(content) {
56 | return isObject(content) && content.constructor === Object;
57 | }
58 | function isOnEvent(str) {
59 | return /^on.+$/.test(str);
60 | }
61 | function isObject(content) {
62 | return typeof content === 'object' && content !== null;
63 | }
64 | function isArray(content) {
65 | return Array.isArray(content);
66 | }
67 |
68 | let updating = false;
69 | const watcherTask = [];
70 | function runUpdate(watcher) {
71 | let index = watcherTask.indexOf(watcher);
72 | if (!(index > -1))
73 | watcherTask.push(watcher);
74 | if (!updating) {
75 | updating = true;
76 | Promise.resolve()
77 | .then(() => {
78 | let watcher = undefined;
79 | while (watcher = watcherTask.shift()) {
80 | watcher.update();
81 | }
82 | }).finally(() => {
83 | updating = false;
84 | });
85 | }
86 | }
87 |
88 | // 目标对象到映射对象
89 | const targetMap = new WeakMap();
90 | // 全局变量watcher
91 | let activeWatcher = null;
92 | const REACTIVE = Symbol('reactive');
93 | /**
94 | * 实现响应式对象
95 | */
96 | function reactive(target) {
97 | if (target[REACTIVE])
98 | return target;
99 | let handler = {
100 | get(target, prop, receiver) {
101 | if (prop === REACTIVE)
102 | return true;
103 | const res = Reflect.get(target, prop, receiver);
104 | if (isObjectExact(res) && !isVNode(res)) {
105 | return reactive(res);
106 | }
107 | if (Array.isArray(res)) {
108 | track(target, prop);
109 | return reactiveArray(res, target, prop);
110 | }
111 | track(target, prop);
112 | return res;
113 | },
114 | set(target, prop, value, receiver) {
115 | const res = Reflect.set(target, prop, value, receiver);
116 | trigger(target, prop);
117 | return res;
118 | }
119 | };
120 | return new Proxy(target, handler);
121 | }
122 | /**
123 | * 设置响应式数组
124 | */
125 | function reactiveArray(targetArr, targetObj, Arrprop) {
126 | let handler = {
127 | get(target, prop, receiver) {
128 | const res = Reflect.get(target, prop, receiver);
129 | if (isObjectExact(res)) {
130 | return reactive(res);
131 | }
132 | return res;
133 | },
134 | set(target, prop, value, receiver) {
135 | const res = Reflect.set(target, prop, value, receiver);
136 | trigger(targetObj, Arrprop);
137 | return res;
138 | }
139 | };
140 | return new Proxy(targetArr, handler);
141 | }
142 | /**
143 | * 响应触发依赖
144 | */
145 | function trigger(target, prop) {
146 | let mapping = targetMap.get(target);
147 | if (!mapping)
148 | return;
149 | let mappingProp = mapping[prop];
150 | if (!mappingProp)
151 | return;
152 | // mappingProp.forEach(watcher => watcher.update(oldValue, newValue))
153 | mappingProp.forEach(watcher => {
154 | // 针对于对数组响应做特殊处理
155 | if (isArray(target[prop])) {
156 | watcher.nextDepArr = target[prop];
157 | }
158 | runUpdate(watcher);
159 | });
160 | }
161 | /**
162 | * 追踪绑定依赖
163 | */
164 | function track(target, prop) {
165 | if (!activeWatcher)
166 | return;
167 | let mapping = targetMap.get(target);
168 | if (!mapping)
169 | targetMap.set(target, mapping = {});
170 | let mappingProp = mapping[prop];
171 | if (!mappingProp)
172 | mappingProp = mapping[prop] = [];
173 | // 针对于对数组响应做特殊处理
174 | if (isArray(target[prop])) {
175 | if (activeWatcher.depArr) {
176 | activeWatcher.depArr = false;
177 | }
178 | else {
179 | if (activeWatcher.depArr === undefined) {
180 | activeWatcher.depArr = target[prop].slice();
181 | }
182 | }
183 | }
184 | mappingProp.push(activeWatcher);
185 | }
186 | // 设置全局变量
187 | function setActiver(fn) {
188 | activeWatcher = fn;
189 | }
190 | // 设置全局变量
191 | function getActiver() {
192 | return activeWatcher;
193 | }
194 |
195 | class RefImpl {
196 | constructor(value) {
197 | this._value = value;
198 | this._target = { value: this._value };
199 | }
200 | get value() {
201 | track(this._target, 'value');
202 | return this._value;
203 | }
204 | set value(value) {
205 | this._value = value;
206 | this._target.value = this._value;
207 | trigger(this._target, 'value');
208 | }
209 | }
210 | function ref(value) {
211 | return new RefImpl(value);
212 | }
213 |
214 | function state(value) {
215 | return ref(value);
216 | }
217 | function defineState(target) {
218 | return reactive(target);
219 | }
220 |
221 | /**
222 | * 响应式对象
223 | */
224 | class Activer {
225 | constructor(fn) {
226 | this.flag = 'activer';
227 | this.callback = fn;
228 | }
229 | get value() {
230 | return this.callback();
231 | }
232 | }
233 | /**
234 | * 外置函数
235 | */
236 | function active(fn) {
237 | return new Activer(fn);
238 | }
239 |
240 | class ComponentLifeCycle {
241 | constructor() {
242 | this.createdList = [];
243 | this.beforeMountedList = [];
244 | this.readyMountedList = [];
245 | this.mountedList = [];
246 | this.beforeUnMountedList = [];
247 | this.unMountedList = [];
248 | }
249 | created(fn) {
250 | this.createdList.push(fn);
251 | }
252 | beforeMounted(fn) {
253 | this.beforeMountedList.push(fn);
254 | }
255 | readyMounted(fn) {
256 | this.readyMountedList.push(fn);
257 | }
258 | mounted(fn) {
259 | this.mountedList.push(fn);
260 | }
261 | beforeUnMounted(fn) {
262 | this.beforeUnMountedList.push(fn);
263 | }
264 | unMounted(fn) {
265 | this.unMountedList.push(fn);
266 | }
267 | emit(lifeName) {
268 | this[`${lifeName}List`].forEach(fn => fn());
269 | }
270 | }
271 | // 生成类组件生命周期实例
272 | function createClassComponentLife(component) {
273 | let lifeNames = ['created', 'beforeMounted', 'readyMounted', 'mounted', 'beforeUnMounted', 'unMounted'];
274 | let lifeCycle = new ComponentLifeCycle();
275 | lifeNames.forEach(lifeName => {
276 | let fn = component[lifeName];
277 | if (!fn)
278 | return;
279 | lifeCycle[lifeName](fn.bind(component));
280 | });
281 | return lifeCycle;
282 | }
283 |
284 | /**
285 | * 函数转化为activer转化为VAlive
286 | * activer转化为VAlive
287 | * string转化为VText
288 | * 数组转化为VArray
289 | * 其他转化为VText
290 | */
291 | function createVNode(originVNode) {
292 | if (isVNode(originVNode))
293 | return originVNode;
294 | if (isFunction(originVNode))
295 | return renderAlive(active(originVNode));
296 | else if (isActiver(originVNode))
297 | return renderAlive(originVNode);
298 | else if (isString(originVNode))
299 | return renderText(originVNode);
300 | else if (isArray(originVNode))
301 | return renderArrayNode(originVNode.map(item => createVNode(item)));
302 | else {
303 | // todo
304 | let value = originVNode;
305 | let retText;
306 | if (!value && typeof value !== 'number')
307 | retText = renderText('');
308 | else
309 | retText = renderText(String(value));
310 | return retText;
311 | }
312 | }
313 | /**
314 | * text(不需要props)、fragment(不需要props)、element、component为显性创建
315 | * array(不需要props)、alive(不需要props)为隐形创建
316 | */
317 | function renderApi(type, props, children) {
318 | return render(type, props || undefined, children);
319 | }
320 | function render(type, originProps, originChildren) {
321 | // text的children比较特殊先处理
322 | if (isText(type)) {
323 | return renderText(String(originChildren));
324 | }
325 | // 预处理 处理为单个的children
326 | let originChildrenList = [];
327 | if (isArray(originChildren))
328 | originChildrenList.push(...originChildren);
329 | else
330 | originChildrenList.push(originChildren);
331 | // 创建VNode列表
332 | let vNodeChildren = originChildrenList.map(originChild => createVNode(originChild));
333 | // 属性预处理
334 | let props = originProps || {};
335 | handleProps(props);
336 | if (isString(type))
337 | return renderElement(type, props, vNodeChildren);
338 | if (isFragment(type))
339 | return renderFragment(vNodeChildren);
340 | if (isArrayNode(type))
341 | return renderArrayNode(vNodeChildren);
342 | if (isFunction(type))
343 | return renderComponent(type, props, vNodeChildren);
344 | throw '传入参数不合法';
345 | }
346 | /**
347 | * 对属性进行预处理
348 | */
349 | function handleProps(originProps) {
350 | for (const prop in originProps) {
351 | // 以on开头的事件不需要处理
352 | if (!isOnEvent(prop) &&
353 | isFunction(originProps[prop])) {
354 | // 如不为on且为函数则判断为响应式
355 | originProps[prop] = active(originProps[prop]);
356 | }
357 | }
358 | return originProps;
359 | }
360 | function renderText(text) {
361 | return {
362 | type: TextSymbol,
363 | flag: VNODE_TYPE.TEXT,
364 | children: text
365 | };
366 | }
367 | function renderElement(tag, props, children) {
368 | return {
369 | type: tag,
370 | flag: VNODE_TYPE.ELEMENT,
371 | props,
372 | children
373 | };
374 | }
375 | function renderFragment(children) {
376 | return {
377 | type: FragmentSymbol,
378 | flag: VNODE_TYPE.FRAGMENT,
379 | children
380 | };
381 | }
382 | function renderArrayNode(children) {
383 | return {
384 | type: ArraySymbol,
385 | flag: VNODE_TYPE.ARRAYNODE,
386 | children
387 | };
388 | }
389 | function createComponentProps(props) {
390 | let componentProps = {};
391 | for (const prop in props) {
392 | let curProp = props[prop];
393 | if (isActiver(curProp)) {
394 | Object.defineProperty(componentProps, prop, {
395 | get() {
396 | return curProp.value;
397 | }
398 | });
399 | }
400 | else {
401 | componentProps[prop] = curProp;
402 | }
403 | }
404 | return componentProps;
405 | }
406 | // 渲染一个活跃的节点
407 | function renderAlive(activer) {
408 | return {
409 | type: AliveSymbol,
410 | flag: VNODE_TYPE.ALIVE,
411 | activer
412 | };
413 | }
414 | // 判断是普通函数还是构造函数
415 | function renderComponent(component, props, children) {
416 | let componentProps = createComponentProps(props);
417 | if (component.prototype &&
418 | component.prototype.render &&
419 | isFunction(component.prototype.render)) {
420 | let ClassComponent = component;
421 | let result = new ClassComponent(componentProps, children);
422 | result.props = componentProps;
423 | result.children = children;
424 | let lifeCycle = createClassComponentLife(result);
425 | let vn = result.render(renderApi);
426 | lifeCycle.emit('created');
427 | let vc = {
428 | type: VComponentSymbol,
429 | root: createVNode(vn),
430 | props: componentProps,
431 | children: children,
432 | flag: VNODE_TYPE.COMPONENT,
433 | lifeStyleInstance: lifeCycle
434 | };
435 | lifeCycle.emit('beforeMounted');
436 | return vc;
437 | }
438 | else {
439 | let FunctionComponent = component;
440 | let lifeCycle = new ComponentLifeCycle();
441 | let vn = FunctionComponent(componentProps, children, lifeCycle);
442 | lifeCycle.emit('created');
443 | let vc = {
444 | type: VComponentSymbol,
445 | root: createVNode(vn),
446 | props: componentProps,
447 | children: children,
448 | flag: VNODE_TYPE.COMPONENT,
449 | lifeStyleInstance: lifeCycle
450 | };
451 | lifeCycle.emit('beforeMounted');
452 | return vc;
453 | }
454 | }
455 |
456 | /**
457 | * 观察者
458 | * 观察数据的变化
459 | */
460 | class Watcher {
461 | constructor(activeProps, callback) {
462 | setActiver(this);
463 | this.value = activeProps.value;
464 | this.callback = callback;
465 | this.activeProps = activeProps;
466 | setActiver(null);
467 | }
468 | update() {
469 | var _a;
470 | let newValue = this.activeProps.value;
471 | let oldValue = this.value;
472 | this.value = newValue;
473 | let meta = { targetPropOldValue: this.depArr, targetPropnewValue: this.nextDepArr };
474 | this.callback(oldValue, newValue, meta);
475 | this.depArr = (_a = this.nextDepArr) === null || _a === void 0 ? void 0 : _a.slice();
476 | }
477 | }
478 | /**
479 | * 监控自定义响应式属性
480 | */
481 | function watch(activeProps, callback) {
482 | if (!isActiver(activeProps))
483 | activeProps = active(activeProps);
484 | return new Watcher(activeProps, function (oldValue, newValue) {
485 | callback(oldValue, newValue);
486 | });
487 | }
488 | /**
489 | * 监控可变状态dom
490 | */
491 | function watchVNode(activeVNode, callback) {
492 | let watcher = new Watcher(activeVNode.activer, function (oldValue, newValue, meta) {
493 | const oldVNode = oldValue;
494 | const newVNode = createVNode(newValue);
495 | // 对于数组节点后期需要记录它的响应式数组用于节点更新
496 | if (oldVNode.flag === VNODE_TYPE.ARRAYNODE) {
497 | oldVNode.depArray = meta === null || meta === void 0 ? void 0 : meta.targetPropOldValue;
498 | }
499 | // 对于数组节点后期需要记录它的响应式数组用于节点更新
500 | if (newVNode.flag === VNODE_TYPE.ARRAYNODE) {
501 | newVNode.depArray = meta === null || meta === void 0 ? void 0 : meta.targetPropnewValue;
502 | }
503 | callback(oldVNode, newVNode);
504 | watcher.value = newVNode;
505 | activeVNode.vnode = newVNode;
506 | });
507 | return watcher.value = createVNode(watcher.value);
508 | }
509 | /**
510 | * 监控可变dom的prop
511 | */
512 | function watchProp(activeProp, callback) {
513 | return new Watcher(activeProp, callback).value;
514 | }
515 |
516 | function mountElement(vnode, container, anchor, app) {
517 | const el = document.createElement(vnode.type);
518 | vnode.el = el;
519 | mountElementProps(vnode);
520 | mountChildren(vnode.children, el, undefined, app);
521 | container.insertBefore(el, anchor);
522 | }
523 | function unmountElement(vnode, container) {
524 | vnode.el.remove();
525 | }
526 | function mountElementProps(vnode) {
527 | let el = vnode.el;
528 | let props = vnode.props;
529 | // 处理标签属性
530 | for (let prop in props) {
531 | let value = props[prop];
532 | if (isActiver(value)) {
533 | let firstValue = watchProp(value, (oldValue, newValue) => patchElementProp(oldValue, newValue, el, prop));
534 | setElementProp(el, prop, firstValue);
535 | }
536 | else {
537 | setElementProp(el, prop, value);
538 | }
539 | }
540 | }
541 | /**
542 | * 处理单个dom属性
543 | */
544 | function setElementProp(el, prop, value) {
545 | if (isOnEvent(prop) && isFunction(value)) {
546 | let pattern = /^on(.+)$/;
547 | let result = prop.match(pattern);
548 | result && el.addEventListener(result[1].toLocaleLowerCase(), value.bind(el));
549 | return;
550 | }
551 | switch (prop) {
552 | case 'className':
553 | el.className = String(value);
554 | break;
555 | case 'style':
556 | if (isObject(value)) {
557 | value = mergeStyle(value);
558 | }
559 | default:
560 | el.setAttribute(prop, value);
561 | }
562 | }
563 | /**
564 | * 将对象形式的style转化为字符串
565 | */
566 | function mergeStyle(style) {
567 | let styleStringList = [];
568 | for (let cssAttr in style) {
569 | styleStringList.push(`${cssAttr}:${style[cssAttr]};`);
570 | }
571 | return styleStringList.join('');
572 | }
573 |
574 | function isSameVNode(oldVNode, newVNode) {
575 | return oldVNode.flag === newVNode.flag;
576 | }
577 | // todo
578 | function getNextSibling(vNode) {
579 | switch (vNode.flag) {
580 | case VNODE_TYPE.TEXT:
581 | case VNODE_TYPE.ELEMENT:
582 | case VNODE_TYPE.ARRAYNODE:
583 | case VNODE_TYPE.FRAGMENT:
584 | return vNode.el.nextSibling;
585 | case VNODE_TYPE.COMPONENT:
586 | return vNode.root.el.nextSibling;
587 | case VNODE_TYPE.ALIVE:
588 | return getNextSibling(vNode.vnode);
589 | }
590 | }
591 | function patch(oldVNode, newVNode, container, app) {
592 | // 如果两个节点引用一样不需要判断
593 | if (oldVNode === newVNode)
594 | return;
595 | // 这里在判断相同类型的节点后可以做进一步优化
596 | if (isSameVNode(oldVNode, newVNode)) {
597 | let flag = oldVNode.flag = newVNode.flag;
598 | if (flag === VNODE_TYPE.TEXT) {
599 | patchText(oldVNode, newVNode);
600 | }
601 | else if (flag === VNODE_TYPE.ARRAYNODE) {
602 | if (app === null || app === void 0 ? void 0 : app.options.arrayDiff) {
603 | patchArrayNodeT(oldVNode, newVNode, container);
604 | }
605 | else {
606 | patchArrayNode(oldVNode, newVNode, container);
607 | }
608 | }
609 | else {
610 | // const nextSibling = oldVNode?.el?.nextSibling
611 | const nextSibling = getNextSibling(oldVNode);
612 | unmount(oldVNode);
613 | mount(newVNode, container, nextSibling);
614 | }
615 | }
616 | else {
617 | // const nextSibling = oldVNode?.el?.nextSibling
618 | const nextSibling = getNextSibling(oldVNode);
619 | unmount(oldVNode);
620 | mount(newVNode, container, nextSibling);
621 | }
622 | }
623 | function patchText(oldVNode, newVNode, container) {
624 | oldVNode.el.nodeValue = newVNode.children;
625 | newVNode.el = oldVNode.el;
626 | }
627 | function patchElementProp(oldValue, newValue, el, prop) {
628 | setElementProp(el, prop, newValue);
629 | }
630 | function patchArrayNodeT(oldVNode, newVNode, container) {
631 | if (!oldVNode.depArray) {
632 | patchArrayNode(oldVNode, newVNode, container);
633 | return;
634 | }
635 | const oldDepArray = oldVNode.depArray;
636 | const newDepArray = newVNode.depArray;
637 | const oldChildren = oldVNode.children;
638 | const newChildren = newVNode.children;
639 | if (!oldDepArray.length || !newDepArray.length) {
640 | patchArrayNode(oldVNode, newVNode, container);
641 | return;
642 | }
643 | newVNode.anchor = oldVNode.anchor;
644 | newVNode.el = oldVNode.el;
645 | // 为映射做初始化
646 | let map = new Map();
647 | oldDepArray.forEach((item, index) => {
648 | let arr = map.get(item);
649 | if (!arr)
650 | map.set(item, arr = []);
651 | arr.push({ node: oldChildren[index], index, used: false });
652 | });
653 | let getOld = (item) => {
654 | let arr = map.get(item);
655 | if (!arr)
656 | return false;
657 | let index = arr.findIndex(alone => !alone.used);
658 | if (index > -1)
659 | return arr[index];
660 | else
661 | return false;
662 | };
663 | let moveOld = (item, node) => {
664 | let arr = map.get(item);
665 | if (!arr)
666 | return;
667 | let index = arr.findIndex(alone => alone === node);
668 | arr.splice(index, 1);
669 | };
670 | let maxIndexSoFar = { node: oldChildren[0], index: 0 };
671 | newDepArray.forEach((item, newIndex) => {
672 | let old = getOld(item);
673 | if (old) {
674 | if (old.index < maxIndexSoFar.index) {
675 | let next;
676 | if (newIndex > 0) {
677 | next = getNextSibling(newChildren[newIndex - 1]);
678 | }
679 | else {
680 | next = getNextSibling(maxIndexSoFar.node);
681 | }
682 | VNodeInsertBefore(container, old.node, next);
683 | // container.insertBefore(old.node.el!, next)
684 | }
685 | else {
686 | maxIndexSoFar = old;
687 | }
688 | newChildren[newIndex] = old.node;
689 | moveOld(item, old);
690 | }
691 | else {
692 | // let next = maxIndexSoFar.node.el!.nextSibling
693 | let next;
694 | if (newIndex > 0) {
695 | next = getNextSibling(newChildren[newIndex - 1]);
696 | }
697 | else {
698 | next = getNextSibling(maxIndexSoFar.node);
699 | }
700 | let newNode = newChildren[newIndex];
701 | mount(newNode, container, next);
702 | // maxIndexSoFar = { node: newNode, index: maxIndexSoFar.index + 1 }
703 | }
704 | });
705 | map.forEach(value => {
706 | value.forEach(item => {
707 | if (!item.used) {
708 | unmount(item.node);
709 | }
710 | });
711 | });
712 | }
713 | function patchArrayNode(oldVNode, newVNode, container) {
714 | const nextSibling = oldVNode.el.nextSibling;
715 | unmount(oldVNode);
716 | mount(newVNode, container, nextSibling);
717 | }
718 | /**
719 | * 将一个虚拟节点挂载到一个锚点前面
720 | */
721 | function VNodeInsertBefore(container, node, next) {
722 | if (node.flag === VNODE_TYPE.ELEMENT || node.flag === VNODE_TYPE.TEXT) {
723 | container.insertBefore(node.el, next);
724 | }
725 | else if (node.flag === VNODE_TYPE.COMPONENT) {
726 | container.insertBefore(node.root.el, next);
727 | }
728 | else if (node.flag === VNODE_TYPE.ARRAYNODE || node.flag === VNODE_TYPE.FRAGMENT) {
729 | let start = node.anchor;
730 | let nextToMove = start === null || start === void 0 ? void 0 : start.nextSibling;
731 | let end = node.el;
732 | while (start !== end) {
733 | container.insertBefore(start, next);
734 | start = nextToMove;
735 | nextToMove = start === null || start === void 0 ? void 0 : start.nextSibling;
736 | }
737 | container.insertBefore(end, next);
738 | }
739 | }
740 |
741 | function mount(vnode, container, anchor, app) {
742 | switch (vnode.flag) {
743 | case VNODE_TYPE.ELEMENT:
744 | mountElement(vnode, container, anchor, app);
745 | break;
746 | case VNODE_TYPE.TEXT:
747 | mountText(vnode, container, anchor);
748 | break;
749 | case VNODE_TYPE.FRAGMENT:
750 | mountFragment(vnode, container, anchor, app);
751 | break;
752 | case VNODE_TYPE.ARRAYNODE:
753 | mountArrayNode(vnode, container, anchor, app);
754 | break;
755 | case VNODE_TYPE.COMPONENT:
756 | mountComponent(vnode, container, anchor, app);
757 | break;
758 | case VNODE_TYPE.ALIVE:
759 | mountAlive(vnode, container, anchor, app);
760 | break;
761 | }
762 | }
763 | function unmount(vnode, container) {
764 | switch (vnode.flag) {
765 | case VNODE_TYPE.ELEMENT:
766 | unmountElement(vnode);
767 | break;
768 | case VNODE_TYPE.TEXT:
769 | unmountText(vnode);
770 | break;
771 | case VNODE_TYPE.FRAGMENT:
772 | unmountFragment(vnode);
773 | break;
774 | case VNODE_TYPE.ARRAYNODE:
775 | unmountArrayNode(vnode);
776 | break;
777 | case VNODE_TYPE.COMPONENT:
778 | unmountComponent(vnode);
779 | break;
780 | }
781 | }
782 | function mountChildren(children, container, anchor, app) {
783 | children.forEach(child => mount(child, container, anchor, app));
784 | }
785 | function mountText(vnode, container, anchor) {
786 | const el = document.createTextNode(vnode.children);
787 | vnode.el = el;
788 | container.insertBefore(el, anchor);
789 | }
790 | function unmountText(vnode, container) {
791 | vnode.el.remove();
792 | }
793 | function mountFragment(vnode, container, anchor, app) {
794 | const start = document.createTextNode('');
795 | const end = document.createTextNode('');
796 | vnode.anchor = start;
797 | vnode.el = end;
798 | container.insertBefore(start, anchor);
799 | mountChildren(vnode.children, container, anchor, app);
800 | container.insertBefore(end, anchor);
801 | }
802 | function unmountFragment(vnode, container) {
803 | const start = vnode.anchor;
804 | const end = vnode.el;
805 | let cur = start;
806 | while (cur && cur !== end) {
807 | let next = cur.nextSibling;
808 | cur.remove();
809 | cur = next;
810 | }
811 | end.remove();
812 | }
813 | function mountArrayNode(vnode, container, anchor, app) {
814 | const start = document.createTextNode('');
815 | const end = document.createTextNode('');
816 | vnode.anchor = start;
817 | vnode.el = end;
818 | container.insertBefore(start, anchor);
819 | mountChildren(vnode.children, container, anchor, app);
820 | container.insertBefore(end, anchor);
821 | }
822 | function unmountArrayNode(vnode, container, anchor) {
823 | const start = vnode.anchor;
824 | const end = vnode.el;
825 | let cur = start;
826 | while (cur && cur !== end) {
827 | let next = cur.nextSibling;
828 | cur.remove();
829 | cur = next;
830 | }
831 | end.remove();
832 | }
833 | function mountComponent(vNode, container, anchor, app) {
834 | const root = vNode.root;
835 | vNode.lifeStyleInstance.emit('readyMounted');
836 | mount(root, container, anchor, app);
837 | vNode.lifeStyleInstance.emit('mounted');
838 | }
839 | function unmountComponent(vNode, container) {
840 | vNode.lifeStyleInstance.emit('beforeUnMounted');
841 | unmount(vNode.root);
842 | vNode.lifeStyleInstance.emit('unMounted');
843 | }
844 | function mountAlive(vnode, container, anchor, app) {
845 | let firstVNode = watchVNode(vnode, (oldVNode, newVNode) => patch(oldVNode, newVNode, container, app));
846 | vnode.vnode = firstVNode;
847 | mount(firstVNode, container, anchor, app);
848 | }
849 |
850 | class Component {
851 | }
852 |
853 | class App {
854 | constructor(vnode, options) {
855 | this.rootVNode = vnode;
856 | this.options = options || {};
857 | }
858 | mount(selector) {
859 | if (selector) {
860 | const el = document.querySelector(selector) || document.body;
861 | mount(this.rootVNode, el, undefined, this);
862 | }
863 | else {
864 | mount(this.rootVNode, document.body, undefined, this);
865 | }
866 | // let container = document.createElement('div')
867 | // mount(this.rootVNode, container, undefined, this)
868 | // el?.replaceWith(...container.childNodes)
869 | }
870 | use(plugin) {
871 | const utils = { state, defineState, h: render };
872 | plugin.install(utils);
873 | return this;
874 | }
875 | }
876 | function createApp(vnode, options) {
877 | return new App(vnode, options);
878 | }
879 |
880 | exports.Activer = Activer;
881 | exports.Component = Component;
882 | exports.RefImpl = RefImpl;
883 | exports.VArray = ArraySymbol;
884 | exports.VComponent = VComponentSymbol;
885 | exports.VFragment = FragmentSymbol;
886 | exports.VText = TextSymbol;
887 | exports.Watcher = Watcher;
888 | exports.active = active;
889 | exports.createApp = createApp;
890 | exports.createVNode = renderApi;
891 | exports["default"] = App;
892 | exports.defineState = defineState;
893 | exports.getActiver = getActiver;
894 | exports.mount = mount;
895 | exports.reactive = reactive;
896 | exports.ref = ref;
897 | exports.render = renderApi;
898 | exports.setActiver = setActiver;
899 | exports.state = state;
900 | exports.track = track;
901 | exports.trigger = trigger;
902 | exports.watch = watch;
903 | exports.watchProp = watchProp;
904 | exports.watchVNode = watchVNode;
905 |
906 | Object.defineProperty(exports, '__esModule', { value: true });
907 |
908 | }));
909 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | declare class RefImpl {
2 | private _value;
3 | private readonly _target;
4 | constructor(value: T);
5 | get value(): T;
6 | set value(value: T);
7 | }
8 | declare function ref(value: T): RefImpl;
9 |
10 | declare function state(value: T): RefImpl;
11 | declare function defineState>(target: T): T;
12 |
13 | /**
14 | * 响应式对象
15 | */
16 | declare class Activer {
17 | callback: () => T;
18 | flag: string;
19 | constructor(fn: () => T);
20 | get value(): T;
21 | }
22 | /**
23 | * 外置函数
24 | */
25 | declare function active(fn: () => T): Activer;
26 |
27 | declare type Meta = {
28 | targetPropOldValue: any;
29 | targetPropnewValue: any;
30 | };
31 | /**
32 | * 观察者
33 | * 观察数据的变化
34 | */
35 | declare class Watcher {
36 | value: T;
37 | callback: (oldValue: T, newValue: T, meta?: Meta) => void;
38 | activeProps: Activer;
39 | depArr?: Array | false;
40 | nextDepArr?: Array;
41 | constructor(activeProps: Activer, callback: (oldValue: T, newValue: T, meta?: Meta) => void);
42 | update(): void;
43 | }
44 | /**
45 | * 监控自定义响应式属性
46 | */
47 | declare function watch(activeProps: (() => T) | Activer, callback: (oldValue: T, newValue: T) => void): Watcher;
48 | /**
49 | * 监控可变状态dom
50 | */
51 | declare function watchVNode(activeVNode: VAlive, callback: (oldVNode: VNode, newVNode: VNode) => void): VNode;
52 | /**
53 | * 监控可变dom的prop
54 | */
55 | declare function watchProp(activeProp: Activer, callback: (oldVNode: VNode, newVNode: VNode) => void): any;
56 |
57 | /**
58 | * 实现响应式对象
59 | */
60 | declare function reactive>(target: T): T;
61 | /**
62 | * 响应触发依赖
63 | */
64 | declare function trigger(target: Record, prop: string | symbol): void;
65 | /**
66 | * 追踪绑定依赖
67 | */
68 | declare function track(target: Record, prop: string | symbol): void;
69 | declare function setActiver(fn: Watcher | null): void;
70 | declare function getActiver(): Watcher | null;
71 |
72 | /**
73 | * 传说中的render函数
74 | */
75 | declare type HSymbol = typeof TextSymbol | typeof FragmentSymbol | typeof AliveSymbol | typeof ArraySymbol;
76 | declare type HType = string | HSymbol | ComponentType;
77 | declare type H = (type: HType, props?: VNodeProps, children?: OriginVNode | Array) => VNode;
78 | /**
79 | * text(不需要props)、fragment(不需要props)、element、component为显性创建
80 | * array(不需要props)、alive(不需要props)为隐形创建
81 | */
82 | declare function renderApi(type: HType, props?: VNodeProps | null, children?: OriginVNode | Array): VNode;
83 |
84 | interface AppUtils {
85 | state: typeof state;
86 | defineState: typeof defineState;
87 | h: typeof renderApi;
88 | watch: typeof watch;
89 | }
90 | interface AppContext {
91 | utils: AppUtils;
92 | }
93 | interface AppPlugin {
94 | install(utils: AppContext): void;
95 | }
96 |
97 | /**
98 | * 根类组件用于类型提示
99 | * 抽象类
100 | */
101 | interface ComponentInstance {
102 | props?: VNodeProps;
103 | children?: Array;
104 | utils: AppUtils;
105 | life?: ComponentLifeCycle;
106 | render(h?: H): OriginVNode;
107 | /** 组件示例创建之后触发 */
108 | created?(): void;
109 | /** 组件示例在渲染之前触发(DOM还没有生成) */
110 | beforeMounted?(): void;
111 | /** dom已经生成到js内存但还没挂载 */
112 | readyMounted?(): void;
113 | /** dom挂载到了页面上*/
114 | mounted?(): void;
115 | /** 卸载之前触发 */
116 | beforeUnMounted?(): void;
117 | /** 卸载之后触发 */
118 | unMounted?(): void;
119 | }
120 | declare abstract class Component implements ComponentInstance {
121 | abstract props?: VNodeProps;
122 | abstract children?: Array;
123 | abstract render(h?: H): VNode;
124 | utils: AppUtils;
125 | abstract life?: ComponentLifeCycle;
126 | /** 组件示例创建之后触发 */
127 | abstract created?(): void;
128 | /** 组件示例在渲染之前触发(DOM还没有生成) */
129 | abstract beforeMounted?(): void;
130 | /** dom已经生成到js内存但还没挂载 */
131 | abstract readyMounted?(): void;
132 | /** dom挂载到了页面上*/
133 | abstract mounted?(): void;
134 | /** 卸载之前触发 */
135 | abstract beforeUnMounted?(): void;
136 | /** 卸载之后触发 */
137 | abstract unMounted?(): void;
138 | }
139 | declare function defineComponent(componentType: FunctionComponentType): FunctionComponentType;
140 |
141 | /**
142 | * 主要实现关于组件的生命周期
143 | */
144 | interface ComponentLifeCycleInstance {
145 | /** 组件示例创建之后触发 */
146 | created(fn: () => void): void;
147 | /** 组件示例在渲染之前触发(DOM还没有生成) */
148 | beforeMounted(fn: () => void): void;
149 | /** dom已经生成到js内存但还没挂载 */
150 | readyMounted(fn: () => void): void;
151 | /** dom挂载到了页面上*/
152 | mounted(fn: () => void): void;
153 | /** 卸载之前触发 */
154 | beforeUnMounted(fn: () => void): void;
155 | /** 卸载之后触发 */
156 | unMounted(fn: () => void): void;
157 | }
158 | declare class ComponentLifeCycle implements ComponentLifeCycleInstance {
159 | private readonly createdList;
160 | private readonly beforeMountedList;
161 | private readonly readyMountedList;
162 | private readonly mountedList;
163 | private readonly beforeUnMountedList;
164 | private readonly unMountedList;
165 | constructor(createdList?: Array<() => void>, beforeMountedList?: Array<() => void>, readyMountedList?: Array<() => void>, mountedList?: Array<() => void>, beforeUnMountedList?: Array<() => void>, unMountedList?: Array<() => void>);
166 | created(fn: () => void): void;
167 | beforeMounted(fn: () => void): void;
168 | readyMounted(fn: () => void): void;
169 | mounted(fn: () => void): void;
170 | beforeUnMounted(fn: () => void): void;
171 | unMounted(fn: () => void): void;
172 | emit(lifeName: keyof ComponentLifeCycleInstance): void;
173 | }
174 |
175 | interface ComponentContext {
176 | props: Record;
177 | children: Array;
178 | life: ComponentLifeCycle;
179 | utils: AppUtils;
180 | }
181 | /** 函数组件类型 */
182 | interface FunctionComponentType {
183 | (context: ComponentContext): OriginVNode;
184 | }
185 | /** 类组件类型 */
186 | interface ClassComponentType {
187 | new (props: Record, children: Array): ComponentInstance;
188 | }
189 | declare type ComponentType = FunctionComponentType | ClassComponentType;
190 | declare const VComponentSymbol: unique symbol;
191 |
192 | /**
193 | * 虚拟dom节点类型枚举
194 | */
195 | declare enum VNODE_TYPE {
196 | ELEMENT = 0,
197 | TEXT = 1,
198 | FRAGMENT = 2,
199 | COMPONENT = 3,
200 | ARRAYNODE = 4,
201 | ALIVE = 5
202 | }
203 | declare type VNodeElement = ChildNode;
204 | /**
205 | * 虚拟dom接口类型
206 | */
207 | interface VNode {
208 | type: string | symbol | ComponentType;
209 | flag: VNODE_TYPE;
210 | props?: Record;
211 | children?: Array | string;
212 | el?: VNodeElement;
213 | anchor?: VNodeElement;
214 | activer?: Activer;
215 | vnode?: VNode;
216 | }
217 | declare type NotFunctionOriginVNode = string | VNode | Array | Activer;
218 | declare type WithFunctionOriginVNode = (() => OriginVNode) | NotFunctionOriginVNode;
219 | declare type OriginVNode = WithFunctionOriginVNode | Exclude;
220 | /** 虚拟节点属性 todo */
221 | interface VNodeProps {
222 | [propName: string]: any;
223 | }
224 |
225 | declare const TextSymbol: unique symbol;
226 |
227 | declare const FragmentSymbol: unique symbol;
228 |
229 | declare const ArraySymbol: unique symbol;
230 |
231 | declare const AliveSymbol: unique symbol;
232 | interface VAlive extends VNode {
233 | type: symbol;
234 | flag: VNODE_TYPE.ELEMENT;
235 | activer: Activer;
236 | vnode: VNode;
237 | }
238 |
239 | interface Options {
240 | arrayDiff?: boolean;
241 | }
242 | declare class App {
243 | rootVNode: VNode;
244 | options: Options;
245 | private pluginList;
246 | constructor(vNode: VNode, options?: Options);
247 | mount(selector?: string | HTMLElement): void;
248 | use(plugin: AppPlugin): this;
249 | }
250 | declare function createApp(vNode: VNode, options?: Options): App;
251 |
252 | declare function mount(vnode: VNode, container: HTMLElement, anchor?: VNodeElement, app?: App): void;
253 |
254 | export { Activer, Component, RefImpl, ArraySymbol as VArray, VComponentSymbol as VComponent, FragmentSymbol as VFragment, TextSymbol as VText, Watcher, active, createApp, renderApi as createVNode, App as default, defineComponent, defineState, getActiver, renderApi as h, mount, reactive, ref, renderApi as render, setActiver, state, track, trigger, watch, watchProp, watchVNode };
255 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | import vact from './src'
2 | export * from './src'
3 | export default vact
4 |
5 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 | module.exports = {
3 | preset: 'ts-jest',
4 | testEnvironment: 'node',
5 | testPathIgnorePatterns: [
6 | '/node_modules',
7 | './demo',
8 | './docs',
9 | './examples',
10 | './lib',
11 | './src',
12 | ],
13 | };
14 |
--------------------------------------------------------------------------------
/lib/boundle.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | const TextSymbol = Symbol('Text');
6 |
7 | const FragmentSymbol = Symbol('Fragment');
8 |
9 | const VComponentSymbol = Symbol('VComponent');
10 |
11 | const ArraySymbol = Symbol('ArrayNode');
12 |
13 | const AliveSymbol = Symbol('Alive');
14 |
15 | /**
16 | * 虚拟dom节点类型枚举
17 | */
18 | var VNODE_TYPE;
19 | (function (VNODE_TYPE) {
20 | // 普通元素节点类型
21 | VNODE_TYPE[VNODE_TYPE["ELEMENT"] = 0] = "ELEMENT";
22 | // 文本节点类型
23 | VNODE_TYPE[VNODE_TYPE["TEXT"] = 1] = "TEXT";
24 | VNODE_TYPE[VNODE_TYPE["FRAGMENT"] = 2] = "FRAGMENT";
25 | VNODE_TYPE[VNODE_TYPE["COMPONENT"] = 3] = "COMPONENT";
26 | VNODE_TYPE[VNODE_TYPE["ARRAYNODE"] = 4] = "ARRAYNODE";
27 | VNODE_TYPE[VNODE_TYPE["ALIVE"] = 5] = "ALIVE";
28 | })(VNODE_TYPE || (VNODE_TYPE = {}));
29 |
30 | function isString(content) {
31 | return typeof content === 'string';
32 | }
33 | function isFunction(content) {
34 | return typeof content === 'function';
35 | }
36 | function isFragment(content) {
37 | return content === FragmentSymbol;
38 | }
39 | function isArrayNode(content) {
40 | return content === ArraySymbol;
41 | }
42 | function isText(content) {
43 | return content === TextSymbol;
44 | }
45 | function isActiver(content) {
46 | return isObject(content) && content.flag === 'activer';
47 | }
48 | function isVNode(content) {
49 | return isObject(content) && content.flag in VNODE_TYPE;
50 | }
51 | function isObjectExact(content) {
52 | return isObject(content) && content.constructor === Object;
53 | }
54 | function isOnEvent(str) {
55 | return /^on.+$/.test(str);
56 | }
57 | function isObject(content) {
58 | return typeof content === 'object' && content !== null;
59 | }
60 | function isArray(content) {
61 | return Array.isArray(content);
62 | }
63 |
64 | let updating = false;
65 | const watcherTask = [];
66 | function runUpdate(watcher) {
67 | let index = watcherTask.indexOf(watcher);
68 | if (!(index > -1))
69 | watcherTask.push(watcher);
70 | if (!updating) {
71 | updating = true;
72 | Promise.resolve()
73 | .then(() => {
74 | let watcher = undefined;
75 | while (watcher = watcherTask.shift()) {
76 | watcher.update();
77 | }
78 | }).finally(() => {
79 | updating = false;
80 | });
81 | }
82 | }
83 |
84 | // 目标对象到映射对象
85 | const targetMap = new WeakMap();
86 | // 全局变量watcher
87 | let activeWatcher = null;
88 | const REACTIVE = Symbol('reactive');
89 | /**
90 | * 实现响应式对象
91 | */
92 | function reactive(target) {
93 | if (target[REACTIVE])
94 | return target;
95 | let handler = {
96 | get(target, prop, receiver) {
97 | if (prop === REACTIVE)
98 | return true;
99 | const res = Reflect.get(target, prop, receiver);
100 | if (isObjectExact(res) && !isVNode(res)) {
101 | return reactive(res);
102 | }
103 | if (Array.isArray(res)) {
104 | track(target, prop);
105 | return reactiveArray(res, target, prop);
106 | }
107 | track(target, prop);
108 | return res;
109 | },
110 | set(target, prop, value, receiver) {
111 | const res = Reflect.set(target, prop, value, receiver);
112 | trigger(target, prop);
113 | return res;
114 | }
115 | };
116 | return new Proxy(target, handler);
117 | }
118 | /**
119 | * 设置响应式数组
120 | */
121 | function reactiveArray(targetArr, targetObj, Arrprop) {
122 | let handler = {
123 | get(target, prop, receiver) {
124 | const res = Reflect.get(target, prop, receiver);
125 | if (isObjectExact(res)) {
126 | return reactive(res);
127 | }
128 | return res;
129 | },
130 | set(target, prop, value, receiver) {
131 | const res = Reflect.set(target, prop, value, receiver);
132 | trigger(targetObj, Arrprop);
133 | return res;
134 | }
135 | };
136 | return new Proxy(targetArr, handler);
137 | }
138 | /**
139 | * 响应触发依赖
140 | */
141 | function trigger(target, prop) {
142 | let mapping = targetMap.get(target);
143 | if (!mapping)
144 | return;
145 | let mappingProp = mapping[prop];
146 | if (!mappingProp)
147 | return;
148 | // mappingProp.forEach(watcher => watcher.update(oldValue, newValue))
149 | mappingProp.forEach(watcher => {
150 | // 针对于对数组响应做特殊处理
151 | if (isArray(target[prop])) {
152 | watcher.nextDepArr = target[prop];
153 | }
154 | runUpdate(watcher);
155 | });
156 | }
157 | /**
158 | * 追踪绑定依赖
159 | */
160 | function track(target, prop) {
161 | if (!activeWatcher)
162 | return;
163 | let mapping = targetMap.get(target);
164 | if (!mapping)
165 | targetMap.set(target, mapping = {});
166 | let mappingProp = mapping[prop];
167 | if (!mappingProp)
168 | mappingProp = mapping[prop] = [];
169 | // 针对于对数组响应做特殊处理
170 | if (isArray(target[prop])) {
171 | if (activeWatcher.depArr) {
172 | activeWatcher.depArr = false;
173 | }
174 | else {
175 | if (activeWatcher.depArr === undefined) {
176 | activeWatcher.depArr = target[prop].slice();
177 | }
178 | }
179 | }
180 | mappingProp.push(activeWatcher);
181 | }
182 | // 设置全局变量
183 | function setActiver(fn) {
184 | activeWatcher = fn;
185 | }
186 | // 设置全局变量
187 | function getActiver() {
188 | return activeWatcher;
189 | }
190 |
191 | class RefImpl {
192 | constructor(value) {
193 | this._value = value;
194 | this._target = { value: this._value };
195 | }
196 | get value() {
197 | track(this._target, 'value');
198 | return this._value;
199 | }
200 | set value(value) {
201 | this._value = value;
202 | this._target.value = this._value;
203 | trigger(this._target, 'value');
204 | }
205 | }
206 | function ref(value) {
207 | return new RefImpl(value);
208 | }
209 |
210 | function state(value) {
211 | return ref(value);
212 | }
213 | function defineState(target) {
214 | return reactive(target);
215 | }
216 |
217 | /**
218 | * 响应式对象
219 | */
220 | class Activer {
221 | constructor(fn) {
222 | this.flag = 'activer';
223 | this.callback = fn;
224 | }
225 | get value() {
226 | return this.callback();
227 | }
228 | }
229 | /**
230 | * 外置函数
231 | */
232 | function active(fn) {
233 | return new Activer(fn);
234 | }
235 |
236 | class ComponentLifeCycle {
237 | constructor(createdList = [], beforeMountedList = [], readyMountedList = [], mountedList = [], beforeUnMountedList = [], unMountedList = []) {
238 | this.createdList = createdList;
239 | this.beforeMountedList = beforeMountedList;
240 | this.readyMountedList = readyMountedList;
241 | this.mountedList = mountedList;
242 | this.beforeUnMountedList = beforeUnMountedList;
243 | this.unMountedList = unMountedList;
244 | // this.createdList = []
245 | // this.beforeMountedList = []
246 | // this.readyMountedList = []
247 | // this.mountedList = []
248 | // this.beforeUnMountedList = []
249 | // this.unMountedList = []
250 | }
251 | created(fn) {
252 | this.createdList.push(fn);
253 | }
254 | beforeMounted(fn) {
255 | this.beforeMountedList.push(fn);
256 | }
257 | readyMounted(fn) {
258 | this.readyMountedList.push(fn);
259 | }
260 | mounted(fn) {
261 | this.mountedList.push(fn);
262 | }
263 | beforeUnMounted(fn) {
264 | this.beforeUnMountedList.push(fn);
265 | }
266 | unMounted(fn) {
267 | this.unMountedList.push(fn);
268 | }
269 | emit(lifeName) {
270 | this[`${lifeName}List`].forEach(fn => fn());
271 | }
272 | }
273 | // 生成类组件生命周期实例
274 | function createClassComponentLife(component) {
275 | let lifeNames = ['created', 'beforeMounted', 'readyMounted', 'mounted', 'beforeUnMounted', 'unMounted'];
276 | let lifeCycle = new ComponentLifeCycle();
277 | component.life = lifeCycle;
278 | lifeNames.forEach(lifeName => {
279 | let fn = component[lifeName];
280 | if (!fn)
281 | return;
282 | lifeCycle[lifeName](fn.bind(component));
283 | });
284 | return lifeCycle;
285 | }
286 |
287 | // 给插件提供的能力
288 | const appUtils = {
289 | state,
290 | defineState,
291 | h: renderApi,
292 | watch
293 | };
294 |
295 | /**
296 | * 函数转化为activer转化为VAlive
297 | * activer转化为VAlive
298 | * string转化为VText
299 | * 数组转化为VArray
300 | * 其他转化为VText
301 | */
302 | function createVNode(originVNode) {
303 | if (isVNode(originVNode))
304 | return originVNode;
305 | if (isFunction(originVNode))
306 | return renderAlive(active(originVNode));
307 | else if (isActiver(originVNode))
308 | return renderAlive(originVNode);
309 | else if (isString(originVNode))
310 | return renderText(originVNode);
311 | else if (isArray(originVNode))
312 | return renderArrayNode(originVNode.map(item => createVNode(item)));
313 | else {
314 | // todo
315 | let value = originVNode;
316 | let retText;
317 | if (!value && typeof value !== 'number')
318 | retText = renderText('');
319 | else
320 | retText = renderText(String(value));
321 | return retText;
322 | }
323 | }
324 | /**
325 | * text(不需要props)、fragment(不需要props)、element、component为显性创建
326 | * array(不需要props)、alive(不需要props)为隐形创建
327 | */
328 | function renderApi(type, props, children) {
329 | return render(type, props || undefined, children);
330 | }
331 | function render(type, originProps, originChildren) {
332 | // text的children比较特殊先处理
333 | if (isText(type)) {
334 | return renderText(String(originChildren));
335 | }
336 | // 预处理 处理为单个的children
337 | let originChildrenList = [];
338 | if (isArray(originChildren))
339 | originChildrenList.push(...originChildren);
340 | else
341 | originChildrenList.push(originChildren);
342 | // 创建VNode列表
343 | let vNodeChildren = originChildrenList.map(originChild => createVNode(originChild));
344 | // 属性预处理
345 | let props = originProps || {};
346 | handleProps(props);
347 | if (isString(type))
348 | return renderElement(type, props, vNodeChildren);
349 | if (isFragment(type))
350 | return renderFragment(vNodeChildren);
351 | if (isArrayNode(type))
352 | return renderArrayNode(vNodeChildren);
353 | if (isFunction(type))
354 | return renderComponent(type, props, vNodeChildren);
355 | throw '传入参数不合法';
356 | }
357 | /**
358 | * 对属性进行预处理
359 | */
360 | function handleProps(originProps) {
361 | for (const prop in originProps) {
362 | // 以on开头的事件不需要处理
363 | if (!isOnEvent(prop) &&
364 | isFunction(originProps[prop])) {
365 | // 如不为on且为函数则判断为响应式
366 | originProps[prop] = active(originProps[prop]);
367 | }
368 | }
369 | return originProps;
370 | }
371 | function renderText(text) {
372 | return {
373 | type: TextSymbol,
374 | flag: VNODE_TYPE.TEXT,
375 | children: text
376 | };
377 | }
378 | function renderElement(tag, props, children) {
379 | return {
380 | type: tag,
381 | flag: VNODE_TYPE.ELEMENT,
382 | props,
383 | children
384 | };
385 | }
386 | function renderFragment(children) {
387 | return {
388 | type: FragmentSymbol,
389 | flag: VNODE_TYPE.FRAGMENT,
390 | children
391 | };
392 | }
393 | function renderArrayNode(children) {
394 | return {
395 | type: ArraySymbol,
396 | flag: VNODE_TYPE.ARRAYNODE,
397 | children
398 | };
399 | }
400 | function createComponentProps(props) {
401 | let componentProps = {};
402 | for (const prop in props) {
403 | let curProp = props[prop];
404 | if (isActiver(curProp)) {
405 | Object.defineProperty(componentProps, prop, {
406 | get() {
407 | return curProp.value;
408 | }
409 | });
410 | }
411 | else {
412 | componentProps[prop] = curProp;
413 | }
414 | }
415 | return componentProps;
416 | }
417 | // 渲染一个活跃的节点
418 | function renderAlive(activer) {
419 | return {
420 | type: AliveSymbol,
421 | flag: VNODE_TYPE.ALIVE,
422 | activer
423 | };
424 | }
425 | // 判断是普通函数还是构造函数
426 | function renderComponent(component, props, children) {
427 | let componentProps = createComponentProps(props);
428 | if (component.prototype &&
429 | component.prototype.render &&
430 | isFunction(component.prototype.render)) {
431 | let ClassComponent = component;
432 | let result = new ClassComponent(componentProps, children);
433 | result.props = componentProps;
434 | result.children = children;
435 | let lifeCycle = createClassComponentLife(result);
436 | let vn = result.render(renderApi);
437 | lifeCycle.emit('created');
438 | let vc = {
439 | type: VComponentSymbol,
440 | root: createVNode(vn),
441 | props: componentProps,
442 | children: children,
443 | flag: VNODE_TYPE.COMPONENT,
444 | lifeStyleInstance: lifeCycle
445 | };
446 | lifeCycle.emit('beforeMounted');
447 | return vc;
448 | }
449 | else {
450 | let FunctionComponent = component;
451 | let lifeCycle = new ComponentLifeCycle();
452 | let vn = FunctionComponent({ props: componentProps, children: children, life: lifeCycle, utils: appUtils });
453 | lifeCycle.emit('created');
454 | let vc = {
455 | type: VComponentSymbol,
456 | root: createVNode(vn),
457 | props: componentProps,
458 | children: children,
459 | flag: VNODE_TYPE.COMPONENT,
460 | lifeStyleInstance: lifeCycle
461 | };
462 | lifeCycle.emit('beforeMounted');
463 | return vc;
464 | }
465 | }
466 |
467 | /**
468 | * 观察者
469 | * 观察数据的变化
470 | */
471 | class Watcher {
472 | constructor(activeProps, callback) {
473 | setActiver(this);
474 | this.value = activeProps.value;
475 | this.callback = callback;
476 | this.activeProps = activeProps;
477 | setActiver(null);
478 | }
479 | update() {
480 | var _a;
481 | let newValue = this.activeProps.value;
482 | let oldValue = this.value;
483 | this.value = newValue;
484 | let meta = { targetPropOldValue: this.depArr, targetPropnewValue: this.nextDepArr };
485 | this.callback(oldValue, newValue, meta);
486 | this.depArr = (_a = this.nextDepArr) === null || _a === void 0 ? void 0 : _a.slice();
487 | }
488 | }
489 | /**
490 | * 监控自定义响应式属性
491 | */
492 | function watch(activeProps, callback) {
493 | if (!isActiver(activeProps))
494 | activeProps = active(activeProps);
495 | return new Watcher(activeProps, function (oldValue, newValue) {
496 | callback(oldValue, newValue);
497 | });
498 | }
499 | /**
500 | * 监控可变状态dom
501 | */
502 | function watchVNode(activeVNode, callback) {
503 | let watcher = new Watcher(activeVNode.activer, function (oldValue, newValue, meta) {
504 | const oldVNode = oldValue;
505 | const newVNode = createVNode(newValue);
506 | // 对于数组节点后期需要记录它的响应式数组用于节点更新
507 | if (oldVNode.flag === VNODE_TYPE.ARRAYNODE) {
508 | oldVNode.depArray = meta === null || meta === void 0 ? void 0 : meta.targetPropOldValue;
509 | }
510 | // 对于数组节点后期需要记录它的响应式数组用于节点更新
511 | if (newVNode.flag === VNODE_TYPE.ARRAYNODE) {
512 | newVNode.depArray = meta === null || meta === void 0 ? void 0 : meta.targetPropnewValue;
513 | }
514 | callback(oldVNode, newVNode);
515 | watcher.value = newVNode;
516 | activeVNode.vnode = newVNode;
517 | });
518 | return watcher.value = createVNode(watcher.value);
519 | }
520 | /**
521 | * 监控可变dom的prop
522 | */
523 | function watchProp(activeProp, callback) {
524 | return new Watcher(activeProp, callback).value;
525 | }
526 |
527 | function mountElement(vnode, container, anchor, app) {
528 | const el = document.createElement(vnode.type);
529 | vnode.el = el;
530 | mountElementProps(vnode);
531 | mountChildren(vnode.children, el, undefined, app);
532 | container.insertBefore(el, anchor);
533 | }
534 | function unmountElement(vnode, container) {
535 | vnode.el.remove();
536 | }
537 | function mountElementProps(vnode) {
538 | let el = vnode.el;
539 | let props = vnode.props;
540 | // 处理标签属性
541 | for (let prop in props) {
542 | let value = props[prop];
543 | if (isActiver(value)) {
544 | let firstValue = watchProp(value, (oldValue, newValue) => patchElementProp(oldValue, newValue, el, prop));
545 | setElementProp(el, prop, firstValue);
546 | }
547 | else {
548 | setElementProp(el, prop, value);
549 | }
550 | }
551 | }
552 | /**
553 | * 处理单个dom属性
554 | */
555 | function setElementProp(el, prop, value) {
556 | if (isOnEvent(prop) && isFunction(value)) {
557 | let pattern = /^on(.+)$/;
558 | let result = prop.match(pattern);
559 | result && el.addEventListener(result[1].toLocaleLowerCase(), value.bind(el));
560 | return;
561 | }
562 | switch (prop) {
563 | case 'className':
564 | el.className = String(value);
565 | break;
566 | case 'style':
567 | if (isObject(value)) {
568 | value = mergeStyle(value);
569 | }
570 | default:
571 | el.setAttribute(prop, value);
572 | }
573 | }
574 | /**
575 | * 将对象形式的style转化为字符串
576 | */
577 | function mergeStyle(style) {
578 | let styleStringList = [];
579 | for (let cssAttr in style) {
580 | styleStringList.push(`${cssAttr}:${style[cssAttr]};`);
581 | }
582 | return styleStringList.join('');
583 | }
584 |
585 | function isSameVNode(oldVNode, newVNode) {
586 | return oldVNode.flag === newVNode.flag;
587 | }
588 | // todo
589 | function getNextSibling(vNode) {
590 | switch (vNode.flag) {
591 | case VNODE_TYPE.TEXT:
592 | case VNODE_TYPE.ELEMENT:
593 | case VNODE_TYPE.ARRAYNODE:
594 | case VNODE_TYPE.FRAGMENT:
595 | return vNode.el.nextSibling;
596 | case VNODE_TYPE.COMPONENT:
597 | return vNode.root.el.nextSibling;
598 | case VNODE_TYPE.ALIVE:
599 | return getNextSibling(vNode.vnode);
600 | }
601 | }
602 | function patch(oldVNode, newVNode, container, app) {
603 | // 如果两个节点引用一样不需要判断
604 | if (oldVNode === newVNode)
605 | return;
606 | // 这里在判断相同类型的节点后可以做进一步优化
607 | if (isSameVNode(oldVNode, newVNode)) {
608 | let flag = oldVNode.flag = newVNode.flag;
609 | if (flag === VNODE_TYPE.TEXT) {
610 | patchText(oldVNode, newVNode);
611 | }
612 | else if (flag === VNODE_TYPE.ARRAYNODE) {
613 | if (app === null || app === void 0 ? void 0 : app.options.arrayDiff) {
614 | patchArrayNodeT(oldVNode, newVNode, container);
615 | }
616 | else {
617 | patchArrayNode(oldVNode, newVNode, container);
618 | }
619 | }
620 | else {
621 | // const nextSibling = oldVNode?.el?.nextSibling
622 | const nextSibling = getNextSibling(oldVNode);
623 | unmount(oldVNode);
624 | mount(newVNode, container, nextSibling);
625 | }
626 | }
627 | else {
628 | // const nextSibling = oldVNode?.el?.nextSibling
629 | const nextSibling = getNextSibling(oldVNode);
630 | unmount(oldVNode);
631 | mount(newVNode, container, nextSibling);
632 | }
633 | }
634 | function patchText(oldVNode, newVNode, container) {
635 | oldVNode.el.nodeValue = newVNode.children;
636 | newVNode.el = oldVNode.el;
637 | }
638 | function patchElementProp(oldValue, newValue, el, prop) {
639 | setElementProp(el, prop, newValue);
640 | }
641 | function patchArrayNodeT(oldVNode, newVNode, container) {
642 | if (!oldVNode.depArray) {
643 | patchArrayNode(oldVNode, newVNode, container);
644 | return;
645 | }
646 | const oldDepArray = oldVNode.depArray;
647 | const newDepArray = newVNode.depArray;
648 | const oldChildren = oldVNode.children;
649 | const newChildren = newVNode.children;
650 | if (!oldDepArray.length || !newDepArray.length) {
651 | patchArrayNode(oldVNode, newVNode, container);
652 | return;
653 | }
654 | newVNode.anchor = oldVNode.anchor;
655 | newVNode.el = oldVNode.el;
656 | // 为映射做初始化
657 | let map = new Map();
658 | oldDepArray.forEach((item, index) => {
659 | let arr = map.get(item);
660 | if (!arr)
661 | map.set(item, arr = []);
662 | arr.push({ node: oldChildren[index], index, used: false });
663 | });
664 | let getOld = (item) => {
665 | let arr = map.get(item);
666 | if (!arr)
667 | return false;
668 | let index = arr.findIndex(alone => !alone.used);
669 | if (index > -1)
670 | return arr[index];
671 | else
672 | return false;
673 | };
674 | let moveOld = (item, node) => {
675 | let arr = map.get(item);
676 | if (!arr)
677 | return;
678 | let index = arr.findIndex(alone => alone === node);
679 | arr.splice(index, 1);
680 | };
681 | let maxIndexSoFar = { node: oldChildren[0], index: 0 };
682 | newDepArray.forEach((item, newIndex) => {
683 | let old = getOld(item);
684 | if (old) {
685 | if (old.index < maxIndexSoFar.index) {
686 | let next;
687 | if (newIndex > 0) {
688 | next = getNextSibling(newChildren[newIndex - 1]);
689 | }
690 | else {
691 | next = getNextSibling(maxIndexSoFar.node);
692 | }
693 | VNodeInsertBefore(container, old.node, next);
694 | // container.insertBefore(old.node.el!, next)
695 | }
696 | else {
697 | maxIndexSoFar = old;
698 | }
699 | newChildren[newIndex] = old.node;
700 | moveOld(item, old);
701 | }
702 | else {
703 | // let next = maxIndexSoFar.node.el!.nextSibling
704 | let next;
705 | if (newIndex > 0) {
706 | next = getNextSibling(newChildren[newIndex - 1]);
707 | }
708 | else {
709 | next = getNextSibling(maxIndexSoFar.node);
710 | }
711 | let newNode = newChildren[newIndex];
712 | mount(newNode, container, next);
713 | // maxIndexSoFar = { node: newNode, index: maxIndexSoFar.index + 1 }
714 | }
715 | });
716 | map.forEach(value => {
717 | value.forEach(item => {
718 | if (!item.used) {
719 | unmount(item.node);
720 | }
721 | });
722 | });
723 | }
724 | function patchArrayNode(oldVNode, newVNode, container) {
725 | const nextSibling = oldVNode.el.nextSibling;
726 | unmount(oldVNode);
727 | mount(newVNode, container, nextSibling);
728 | }
729 | /**
730 | * 将一个虚拟节点挂载到一个锚点前面
731 | */
732 | function VNodeInsertBefore(container, node, next) {
733 | if (node.flag === VNODE_TYPE.ELEMENT || node.flag === VNODE_TYPE.TEXT) {
734 | container.insertBefore(node.el, next);
735 | }
736 | else if (node.flag === VNODE_TYPE.COMPONENT) {
737 | container.insertBefore(node.root.el, next);
738 | }
739 | else if (node.flag === VNODE_TYPE.ARRAYNODE || node.flag === VNODE_TYPE.FRAGMENT) {
740 | let start = node.anchor;
741 | let nextToMove = start === null || start === void 0 ? void 0 : start.nextSibling;
742 | let end = node.el;
743 | while (start !== end) {
744 | container.insertBefore(start, next);
745 | start = nextToMove;
746 | nextToMove = start === null || start === void 0 ? void 0 : start.nextSibling;
747 | }
748 | container.insertBefore(end, next);
749 | }
750 | }
751 |
752 | function mount(vnode, container, anchor, app) {
753 | switch (vnode.flag) {
754 | case VNODE_TYPE.ELEMENT:
755 | mountElement(vnode, container, anchor, app);
756 | break;
757 | case VNODE_TYPE.TEXT:
758 | mountText(vnode, container, anchor);
759 | break;
760 | case VNODE_TYPE.FRAGMENT:
761 | mountFragment(vnode, container, anchor, app);
762 | break;
763 | case VNODE_TYPE.ARRAYNODE:
764 | mountArrayNode(vnode, container, anchor, app);
765 | break;
766 | case VNODE_TYPE.COMPONENT:
767 | mountComponent(vnode, container, anchor, app);
768 | break;
769 | case VNODE_TYPE.ALIVE:
770 | mountAlive(vnode, container, anchor, app);
771 | break;
772 | }
773 | }
774 | function unmount(vnode, container) {
775 | switch (vnode.flag) {
776 | case VNODE_TYPE.ELEMENT:
777 | unmountElement(vnode);
778 | break;
779 | case VNODE_TYPE.TEXT:
780 | unmountText(vnode);
781 | break;
782 | case VNODE_TYPE.FRAGMENT:
783 | unmountFragment(vnode);
784 | break;
785 | case VNODE_TYPE.ARRAYNODE:
786 | unmountArrayNode(vnode);
787 | break;
788 | case VNODE_TYPE.COMPONENT:
789 | unmountComponent(vnode);
790 | break;
791 | }
792 | }
793 | function mountChildren(children, container, anchor, app) {
794 | children.forEach(child => mount(child, container, anchor, app));
795 | }
796 | function mountText(vnode, container, anchor) {
797 | const el = document.createTextNode(vnode.children);
798 | vnode.el = el;
799 | container.insertBefore(el, anchor);
800 | }
801 | function unmountText(vnode, container) {
802 | vnode.el.remove();
803 | }
804 | function mountFragment(vnode, container, anchor, app) {
805 | const start = document.createTextNode('');
806 | const end = document.createTextNode('');
807 | vnode.anchor = start;
808 | vnode.el = end;
809 | container.insertBefore(start, anchor);
810 | mountChildren(vnode.children, container, anchor, app);
811 | container.insertBefore(end, anchor);
812 | }
813 | function unmountFragment(vnode, container) {
814 | const start = vnode.anchor;
815 | const end = vnode.el;
816 | let cur = start;
817 | while (cur && cur !== end) {
818 | let next = cur.nextSibling;
819 | cur.remove();
820 | cur = next;
821 | }
822 | end.remove();
823 | }
824 | function mountArrayNode(vnode, container, anchor, app) {
825 | const start = document.createTextNode('');
826 | const end = document.createTextNode('');
827 | vnode.anchor = start;
828 | vnode.el = end;
829 | container.insertBefore(start, anchor);
830 | mountChildren(vnode.children, container, anchor, app);
831 | container.insertBefore(end, anchor);
832 | }
833 | function unmountArrayNode(vnode, container, anchor) {
834 | const start = vnode.anchor;
835 | const end = vnode.el;
836 | let cur = start;
837 | while (cur && cur !== end) {
838 | let next = cur.nextSibling;
839 | cur.remove();
840 | cur = next;
841 | }
842 | end.remove();
843 | }
844 | function mountComponent(vNode, container, anchor, app) {
845 | const root = vNode.root;
846 | vNode.lifeStyleInstance.emit('readyMounted');
847 | mount(root, container, anchor, app);
848 | vNode.lifeStyleInstance.emit('mounted');
849 | }
850 | function unmountComponent(vNode, container) {
851 | vNode.lifeStyleInstance.emit('beforeUnMounted');
852 | unmount(vNode.root);
853 | vNode.lifeStyleInstance.emit('unMounted');
854 | }
855 | function mountAlive(vnode, container, anchor, app) {
856 | let firstVNode = watchVNode(vnode, (oldVNode, newVNode) => patch(oldVNode, newVNode, container, app));
857 | vnode.vnode = firstVNode;
858 | mount(firstVNode, container, anchor, app);
859 | }
860 |
861 | class Component {
862 | constructor() {
863 | this.utils = appUtils;
864 | }
865 | }
866 | function defineComponent(componentType) {
867 | return componentType;
868 | }
869 |
870 | class App {
871 | constructor(vNode, options) {
872 | this.rootVNode = vNode;
873 | this.options = options || {};
874 | this.pluginList = [];
875 | }
876 | mount(selector) {
877 | if (selector) {
878 | const el = (isString(selector) ? document.querySelector(selector) : selector) || document.body;
879 | mount(this.rootVNode, el, undefined, this);
880 | }
881 | else {
882 | mount(this.rootVNode, document.body, undefined, this);
883 | }
884 | // let container = document.createElement('div')
885 | // mount(this.rootVNode, container, undefined, this)
886 | // el?.replaceWith(...container.childNodes)
887 | }
888 | use(plugin) {
889 | let index = this.pluginList.indexOf(plugin);
890 | if (index > -1)
891 | return this;
892 | plugin.install({ utils: appUtils });
893 | return this;
894 | }
895 | }
896 | function createApp(vNode, options) {
897 | return new App(vNode, options);
898 | }
899 |
900 | exports.Activer = Activer;
901 | exports.Component = Component;
902 | exports.RefImpl = RefImpl;
903 | exports.VArray = ArraySymbol;
904 | exports.VComponent = VComponentSymbol;
905 | exports.VFragment = FragmentSymbol;
906 | exports.VText = TextSymbol;
907 | exports.Watcher = Watcher;
908 | exports.active = active;
909 | exports.createApp = createApp;
910 | exports.createVNode = renderApi;
911 | exports["default"] = App;
912 | exports.defineComponent = defineComponent;
913 | exports.defineState = defineState;
914 | exports.getActiver = getActiver;
915 | exports.h = renderApi;
916 | exports.mount = mount;
917 | exports.reactive = reactive;
918 | exports.ref = ref;
919 | exports.render = renderApi;
920 | exports.setActiver = setActiver;
921 | exports.state = state;
922 | exports.track = track;
923 | exports.trigger = trigger;
924 | exports.watch = watch;
925 | exports.watchProp = watchProp;
926 | exports.watchVNode = watchVNode;
927 | //# sourceMappingURL=boundle.js.map
928 |
--------------------------------------------------------------------------------
/lib/boundle.umd.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3 | typeof define === 'function' && define.amd ? define(['exports'], factory) :
4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Vact = {}));
5 | })(this, (function (exports) { 'use strict';
6 |
7 | const TextSymbol = Symbol('Text');
8 |
9 | const FragmentSymbol = Symbol('Fragment');
10 |
11 | const VComponentSymbol = Symbol('VComponent');
12 |
13 | const ArraySymbol = Symbol('ArrayNode');
14 |
15 | const AliveSymbol = Symbol('Alive');
16 |
17 | /**
18 | * 虚拟dom节点类型枚举
19 | */
20 | var VNODE_TYPE;
21 | (function (VNODE_TYPE) {
22 | // 普通元素节点类型
23 | VNODE_TYPE[VNODE_TYPE["ELEMENT"] = 0] = "ELEMENT";
24 | // 文本节点类型
25 | VNODE_TYPE[VNODE_TYPE["TEXT"] = 1] = "TEXT";
26 | VNODE_TYPE[VNODE_TYPE["FRAGMENT"] = 2] = "FRAGMENT";
27 | VNODE_TYPE[VNODE_TYPE["COMPONENT"] = 3] = "COMPONENT";
28 | VNODE_TYPE[VNODE_TYPE["ARRAYNODE"] = 4] = "ARRAYNODE";
29 | VNODE_TYPE[VNODE_TYPE["ALIVE"] = 5] = "ALIVE";
30 | })(VNODE_TYPE || (VNODE_TYPE = {}));
31 |
32 | function isString(content) {
33 | return typeof content === 'string';
34 | }
35 | function isFunction(content) {
36 | return typeof content === 'function';
37 | }
38 | function isFragment(content) {
39 | return content === FragmentSymbol;
40 | }
41 | function isArrayNode(content) {
42 | return content === ArraySymbol;
43 | }
44 | function isText(content) {
45 | return content === TextSymbol;
46 | }
47 | function isActiver(content) {
48 | return isObject(content) && content.flag === 'activer';
49 | }
50 | function isVNode(content) {
51 | return isObject(content) && content.flag in VNODE_TYPE;
52 | }
53 | function isObjectExact(content) {
54 | return isObject(content) && content.constructor === Object;
55 | }
56 | function isOnEvent(str) {
57 | return /^on.+$/.test(str);
58 | }
59 | function isObject(content) {
60 | return typeof content === 'object' && content !== null;
61 | }
62 | function isArray(content) {
63 | return Array.isArray(content);
64 | }
65 |
66 | let updating = false;
67 | const watcherTask = [];
68 | function runUpdate(watcher) {
69 | let index = watcherTask.indexOf(watcher);
70 | if (!(index > -1))
71 | watcherTask.push(watcher);
72 | if (!updating) {
73 | updating = true;
74 | Promise.resolve()
75 | .then(() => {
76 | let watcher = undefined;
77 | while (watcher = watcherTask.shift()) {
78 | watcher.update();
79 | }
80 | }).finally(() => {
81 | updating = false;
82 | });
83 | }
84 | }
85 |
86 | // 目标对象到映射对象
87 | const targetMap = new WeakMap();
88 | // 全局变量watcher
89 | let activeWatcher = null;
90 | const REACTIVE = Symbol('reactive');
91 | /**
92 | * 实现响应式对象
93 | */
94 | function reactive(target) {
95 | if (target[REACTIVE])
96 | return target;
97 | let handler = {
98 | get(target, prop, receiver) {
99 | if (prop === REACTIVE)
100 | return true;
101 | const res = Reflect.get(target, prop, receiver);
102 | if (isObjectExact(res) && !isVNode(res)) {
103 | return reactive(res);
104 | }
105 | if (Array.isArray(res)) {
106 | track(target, prop);
107 | return reactiveArray(res, target, prop);
108 | }
109 | track(target, prop);
110 | return res;
111 | },
112 | set(target, prop, value, receiver) {
113 | const res = Reflect.set(target, prop, value, receiver);
114 | trigger(target, prop);
115 | return res;
116 | }
117 | };
118 | return new Proxy(target, handler);
119 | }
120 | /**
121 | * 设置响应式数组
122 | */
123 | function reactiveArray(targetArr, targetObj, Arrprop) {
124 | let handler = {
125 | get(target, prop, receiver) {
126 | const res = Reflect.get(target, prop, receiver);
127 | if (isObjectExact(res)) {
128 | return reactive(res);
129 | }
130 | return res;
131 | },
132 | set(target, prop, value, receiver) {
133 | const res = Reflect.set(target, prop, value, receiver);
134 | trigger(targetObj, Arrprop);
135 | return res;
136 | }
137 | };
138 | return new Proxy(targetArr, handler);
139 | }
140 | /**
141 | * 响应触发依赖
142 | */
143 | function trigger(target, prop) {
144 | let mapping = targetMap.get(target);
145 | if (!mapping)
146 | return;
147 | let mappingProp = mapping[prop];
148 | if (!mappingProp)
149 | return;
150 | // mappingProp.forEach(watcher => watcher.update(oldValue, newValue))
151 | mappingProp.forEach(watcher => {
152 | // 针对于对数组响应做特殊处理
153 | if (isArray(target[prop])) {
154 | watcher.nextDepArr = target[prop];
155 | }
156 | runUpdate(watcher);
157 | });
158 | }
159 | /**
160 | * 追踪绑定依赖
161 | */
162 | function track(target, prop) {
163 | if (!activeWatcher)
164 | return;
165 | let mapping = targetMap.get(target);
166 | if (!mapping)
167 | targetMap.set(target, mapping = {});
168 | let mappingProp = mapping[prop];
169 | if (!mappingProp)
170 | mappingProp = mapping[prop] = [];
171 | // 针对于对数组响应做特殊处理
172 | if (isArray(target[prop])) {
173 | if (activeWatcher.depArr) {
174 | activeWatcher.depArr = false;
175 | }
176 | else {
177 | if (activeWatcher.depArr === undefined) {
178 | activeWatcher.depArr = target[prop].slice();
179 | }
180 | }
181 | }
182 | mappingProp.push(activeWatcher);
183 | }
184 | // 设置全局变量
185 | function setActiver(fn) {
186 | activeWatcher = fn;
187 | }
188 | // 设置全局变量
189 | function getActiver() {
190 | return activeWatcher;
191 | }
192 |
193 | class RefImpl {
194 | constructor(value) {
195 | this._value = value;
196 | this._target = { value: this._value };
197 | }
198 | get value() {
199 | track(this._target, 'value');
200 | return this._value;
201 | }
202 | set value(value) {
203 | this._value = value;
204 | this._target.value = this._value;
205 | trigger(this._target, 'value');
206 | }
207 | }
208 | function ref(value) {
209 | return new RefImpl(value);
210 | }
211 |
212 | function state(value) {
213 | return ref(value);
214 | }
215 | function defineState(target) {
216 | return reactive(target);
217 | }
218 |
219 | /**
220 | * 响应式对象
221 | */
222 | class Activer {
223 | constructor(fn) {
224 | this.flag = 'activer';
225 | this.callback = fn;
226 | }
227 | get value() {
228 | return this.callback();
229 | }
230 | }
231 | /**
232 | * 外置函数
233 | */
234 | function active(fn) {
235 | return new Activer(fn);
236 | }
237 |
238 | class ComponentLifeCycle {
239 | constructor(createdList = [], beforeMountedList = [], readyMountedList = [], mountedList = [], beforeUnMountedList = [], unMountedList = []) {
240 | this.createdList = createdList;
241 | this.beforeMountedList = beforeMountedList;
242 | this.readyMountedList = readyMountedList;
243 | this.mountedList = mountedList;
244 | this.beforeUnMountedList = beforeUnMountedList;
245 | this.unMountedList = unMountedList;
246 | // this.createdList = []
247 | // this.beforeMountedList = []
248 | // this.readyMountedList = []
249 | // this.mountedList = []
250 | // this.beforeUnMountedList = []
251 | // this.unMountedList = []
252 | }
253 | created(fn) {
254 | this.createdList.push(fn);
255 | }
256 | beforeMounted(fn) {
257 | this.beforeMountedList.push(fn);
258 | }
259 | readyMounted(fn) {
260 | this.readyMountedList.push(fn);
261 | }
262 | mounted(fn) {
263 | this.mountedList.push(fn);
264 | }
265 | beforeUnMounted(fn) {
266 | this.beforeUnMountedList.push(fn);
267 | }
268 | unMounted(fn) {
269 | this.unMountedList.push(fn);
270 | }
271 | emit(lifeName) {
272 | this[`${lifeName}List`].forEach(fn => fn());
273 | }
274 | }
275 | // 生成类组件生命周期实例
276 | function createClassComponentLife(component) {
277 | let lifeNames = ['created', 'beforeMounted', 'readyMounted', 'mounted', 'beforeUnMounted', 'unMounted'];
278 | let lifeCycle = new ComponentLifeCycle();
279 | component.life = lifeCycle;
280 | lifeNames.forEach(lifeName => {
281 | let fn = component[lifeName];
282 | if (!fn)
283 | return;
284 | lifeCycle[lifeName](fn.bind(component));
285 | });
286 | return lifeCycle;
287 | }
288 |
289 | // 给插件提供的能力
290 | const appUtils = {
291 | state,
292 | defineState,
293 | h: renderApi,
294 | watch
295 | };
296 |
297 | /**
298 | * 函数转化为activer转化为VAlive
299 | * activer转化为VAlive
300 | * string转化为VText
301 | * 数组转化为VArray
302 | * 其他转化为VText
303 | */
304 | function createVNode(originVNode) {
305 | if (isVNode(originVNode))
306 | return originVNode;
307 | if (isFunction(originVNode))
308 | return renderAlive(active(originVNode));
309 | else if (isActiver(originVNode))
310 | return renderAlive(originVNode);
311 | else if (isString(originVNode))
312 | return renderText(originVNode);
313 | else if (isArray(originVNode))
314 | return renderArrayNode(originVNode.map(item => createVNode(item)));
315 | else {
316 | // todo
317 | let value = originVNode;
318 | let retText;
319 | if (!value && typeof value !== 'number')
320 | retText = renderText('');
321 | else
322 | retText = renderText(String(value));
323 | return retText;
324 | }
325 | }
326 | /**
327 | * text(不需要props)、fragment(不需要props)、element、component为显性创建
328 | * array(不需要props)、alive(不需要props)为隐形创建
329 | */
330 | function renderApi(type, props, children) {
331 | return render(type, props || undefined, children);
332 | }
333 | function render(type, originProps, originChildren) {
334 | // text的children比较特殊先处理
335 | if (isText(type)) {
336 | return renderText(String(originChildren));
337 | }
338 | // 预处理 处理为单个的children
339 | let originChildrenList = [];
340 | if (isArray(originChildren))
341 | originChildrenList.push(...originChildren);
342 | else
343 | originChildrenList.push(originChildren);
344 | // 创建VNode列表
345 | let vNodeChildren = originChildrenList.map(originChild => createVNode(originChild));
346 | // 属性预处理
347 | let props = originProps || {};
348 | handleProps(props);
349 | if (isString(type))
350 | return renderElement(type, props, vNodeChildren);
351 | if (isFragment(type))
352 | return renderFragment(vNodeChildren);
353 | if (isArrayNode(type))
354 | return renderArrayNode(vNodeChildren);
355 | if (isFunction(type))
356 | return renderComponent(type, props, vNodeChildren);
357 | throw '传入参数不合法';
358 | }
359 | /**
360 | * 对属性进行预处理
361 | */
362 | function handleProps(originProps) {
363 | for (const prop in originProps) {
364 | // 以on开头的事件不需要处理
365 | if (!isOnEvent(prop) &&
366 | isFunction(originProps[prop])) {
367 | // 如不为on且为函数则判断为响应式
368 | originProps[prop] = active(originProps[prop]);
369 | }
370 | }
371 | return originProps;
372 | }
373 | function renderText(text) {
374 | return {
375 | type: TextSymbol,
376 | flag: VNODE_TYPE.TEXT,
377 | children: text
378 | };
379 | }
380 | function renderElement(tag, props, children) {
381 | return {
382 | type: tag,
383 | flag: VNODE_TYPE.ELEMENT,
384 | props,
385 | children
386 | };
387 | }
388 | function renderFragment(children) {
389 | return {
390 | type: FragmentSymbol,
391 | flag: VNODE_TYPE.FRAGMENT,
392 | children
393 | };
394 | }
395 | function renderArrayNode(children) {
396 | return {
397 | type: ArraySymbol,
398 | flag: VNODE_TYPE.ARRAYNODE,
399 | children
400 | };
401 | }
402 | function createComponentProps(props) {
403 | let componentProps = {};
404 | for (const prop in props) {
405 | let curProp = props[prop];
406 | if (isActiver(curProp)) {
407 | Object.defineProperty(componentProps, prop, {
408 | get() {
409 | return curProp.value;
410 | }
411 | });
412 | }
413 | else {
414 | componentProps[prop] = curProp;
415 | }
416 | }
417 | return componentProps;
418 | }
419 | // 渲染一个活跃的节点
420 | function renderAlive(activer) {
421 | return {
422 | type: AliveSymbol,
423 | flag: VNODE_TYPE.ALIVE,
424 | activer
425 | };
426 | }
427 | // 判断是普通函数还是构造函数
428 | function renderComponent(component, props, children) {
429 | let componentProps = createComponentProps(props);
430 | if (component.prototype &&
431 | component.prototype.render &&
432 | isFunction(component.prototype.render)) {
433 | let ClassComponent = component;
434 | let result = new ClassComponent(componentProps, children);
435 | result.props = componentProps;
436 | result.children = children;
437 | let lifeCycle = createClassComponentLife(result);
438 | let vn = result.render(renderApi);
439 | lifeCycle.emit('created');
440 | let vc = {
441 | type: VComponentSymbol,
442 | root: createVNode(vn),
443 | props: componentProps,
444 | children: children,
445 | flag: VNODE_TYPE.COMPONENT,
446 | lifeStyleInstance: lifeCycle
447 | };
448 | lifeCycle.emit('beforeMounted');
449 | return vc;
450 | }
451 | else {
452 | let FunctionComponent = component;
453 | let lifeCycle = new ComponentLifeCycle();
454 | let vn = FunctionComponent({ props: componentProps, children: children, life: lifeCycle, utils: appUtils });
455 | lifeCycle.emit('created');
456 | let vc = {
457 | type: VComponentSymbol,
458 | root: createVNode(vn),
459 | props: componentProps,
460 | children: children,
461 | flag: VNODE_TYPE.COMPONENT,
462 | lifeStyleInstance: lifeCycle
463 | };
464 | lifeCycle.emit('beforeMounted');
465 | return vc;
466 | }
467 | }
468 |
469 | /**
470 | * 观察者
471 | * 观察数据的变化
472 | */
473 | class Watcher {
474 | constructor(activeProps, callback) {
475 | setActiver(this);
476 | this.value = activeProps.value;
477 | this.callback = callback;
478 | this.activeProps = activeProps;
479 | setActiver(null);
480 | }
481 | update() {
482 | var _a;
483 | let newValue = this.activeProps.value;
484 | let oldValue = this.value;
485 | this.value = newValue;
486 | let meta = { targetPropOldValue: this.depArr, targetPropnewValue: this.nextDepArr };
487 | this.callback(oldValue, newValue, meta);
488 | this.depArr = (_a = this.nextDepArr) === null || _a === void 0 ? void 0 : _a.slice();
489 | }
490 | }
491 | /**
492 | * 监控自定义响应式属性
493 | */
494 | function watch(activeProps, callback) {
495 | if (!isActiver(activeProps))
496 | activeProps = active(activeProps);
497 | return new Watcher(activeProps, function (oldValue, newValue) {
498 | callback(oldValue, newValue);
499 | });
500 | }
501 | /**
502 | * 监控可变状态dom
503 | */
504 | function watchVNode(activeVNode, callback) {
505 | let watcher = new Watcher(activeVNode.activer, function (oldValue, newValue, meta) {
506 | const oldVNode = oldValue;
507 | const newVNode = createVNode(newValue);
508 | // 对于数组节点后期需要记录它的响应式数组用于节点更新
509 | if (oldVNode.flag === VNODE_TYPE.ARRAYNODE) {
510 | oldVNode.depArray = meta === null || meta === void 0 ? void 0 : meta.targetPropOldValue;
511 | }
512 | // 对于数组节点后期需要记录它的响应式数组用于节点更新
513 | if (newVNode.flag === VNODE_TYPE.ARRAYNODE) {
514 | newVNode.depArray = meta === null || meta === void 0 ? void 0 : meta.targetPropnewValue;
515 | }
516 | callback(oldVNode, newVNode);
517 | watcher.value = newVNode;
518 | activeVNode.vnode = newVNode;
519 | });
520 | return watcher.value = createVNode(watcher.value);
521 | }
522 | /**
523 | * 监控可变dom的prop
524 | */
525 | function watchProp(activeProp, callback) {
526 | return new Watcher(activeProp, callback).value;
527 | }
528 |
529 | function mountElement(vnode, container, anchor, app) {
530 | const el = document.createElement(vnode.type);
531 | vnode.el = el;
532 | mountElementProps(vnode);
533 | mountChildren(vnode.children, el, undefined, app);
534 | container.insertBefore(el, anchor);
535 | }
536 | function unmountElement(vnode, container) {
537 | vnode.el.remove();
538 | }
539 | function mountElementProps(vnode) {
540 | let el = vnode.el;
541 | let props = vnode.props;
542 | // 处理标签属性
543 | for (let prop in props) {
544 | let value = props[prop];
545 | if (isActiver(value)) {
546 | let firstValue = watchProp(value, (oldValue, newValue) => patchElementProp(oldValue, newValue, el, prop));
547 | setElementProp(el, prop, firstValue);
548 | }
549 | else {
550 | setElementProp(el, prop, value);
551 | }
552 | }
553 | }
554 | /**
555 | * 处理单个dom属性
556 | */
557 | function setElementProp(el, prop, value) {
558 | if (isOnEvent(prop) && isFunction(value)) {
559 | let pattern = /^on(.+)$/;
560 | let result = prop.match(pattern);
561 | result && el.addEventListener(result[1].toLocaleLowerCase(), value.bind(el));
562 | return;
563 | }
564 | switch (prop) {
565 | case 'className':
566 | el.className = String(value);
567 | break;
568 | case 'style':
569 | if (isObject(value)) {
570 | value = mergeStyle(value);
571 | }
572 | default:
573 | el.setAttribute(prop, value);
574 | }
575 | }
576 | /**
577 | * 将对象形式的style转化为字符串
578 | */
579 | function mergeStyle(style) {
580 | let styleStringList = [];
581 | for (let cssAttr in style) {
582 | styleStringList.push(`${cssAttr}:${style[cssAttr]};`);
583 | }
584 | return styleStringList.join('');
585 | }
586 |
587 | function isSameVNode(oldVNode, newVNode) {
588 | return oldVNode.flag === newVNode.flag;
589 | }
590 | // todo
591 | function getNextSibling(vNode) {
592 | switch (vNode.flag) {
593 | case VNODE_TYPE.TEXT:
594 | case VNODE_TYPE.ELEMENT:
595 | case VNODE_TYPE.ARRAYNODE:
596 | case VNODE_TYPE.FRAGMENT:
597 | return vNode.el.nextSibling;
598 | case VNODE_TYPE.COMPONENT:
599 | return vNode.root.el.nextSibling;
600 | case VNODE_TYPE.ALIVE:
601 | return getNextSibling(vNode.vnode);
602 | }
603 | }
604 | function patch(oldVNode, newVNode, container, app) {
605 | // 如果两个节点引用一样不需要判断
606 | if (oldVNode === newVNode)
607 | return;
608 | // 这里在判断相同类型的节点后可以做进一步优化
609 | if (isSameVNode(oldVNode, newVNode)) {
610 | let flag = oldVNode.flag = newVNode.flag;
611 | if (flag === VNODE_TYPE.TEXT) {
612 | patchText(oldVNode, newVNode);
613 | }
614 | else if (flag === VNODE_TYPE.ARRAYNODE) {
615 | if (app === null || app === void 0 ? void 0 : app.options.arrayDiff) {
616 | patchArrayNodeT(oldVNode, newVNode, container);
617 | }
618 | else {
619 | patchArrayNode(oldVNode, newVNode, container);
620 | }
621 | }
622 | else {
623 | // const nextSibling = oldVNode?.el?.nextSibling
624 | const nextSibling = getNextSibling(oldVNode);
625 | unmount(oldVNode);
626 | mount(newVNode, container, nextSibling);
627 | }
628 | }
629 | else {
630 | // const nextSibling = oldVNode?.el?.nextSibling
631 | const nextSibling = getNextSibling(oldVNode);
632 | unmount(oldVNode);
633 | mount(newVNode, container, nextSibling);
634 | }
635 | }
636 | function patchText(oldVNode, newVNode, container) {
637 | oldVNode.el.nodeValue = newVNode.children;
638 | newVNode.el = oldVNode.el;
639 | }
640 | function patchElementProp(oldValue, newValue, el, prop) {
641 | setElementProp(el, prop, newValue);
642 | }
643 | function patchArrayNodeT(oldVNode, newVNode, container) {
644 | if (!oldVNode.depArray) {
645 | patchArrayNode(oldVNode, newVNode, container);
646 | return;
647 | }
648 | const oldDepArray = oldVNode.depArray;
649 | const newDepArray = newVNode.depArray;
650 | const oldChildren = oldVNode.children;
651 | const newChildren = newVNode.children;
652 | if (!oldDepArray.length || !newDepArray.length) {
653 | patchArrayNode(oldVNode, newVNode, container);
654 | return;
655 | }
656 | newVNode.anchor = oldVNode.anchor;
657 | newVNode.el = oldVNode.el;
658 | // 为映射做初始化
659 | let map = new Map();
660 | oldDepArray.forEach((item, index) => {
661 | let arr = map.get(item);
662 | if (!arr)
663 | map.set(item, arr = []);
664 | arr.push({ node: oldChildren[index], index, used: false });
665 | });
666 | let getOld = (item) => {
667 | let arr = map.get(item);
668 | if (!arr)
669 | return false;
670 | let index = arr.findIndex(alone => !alone.used);
671 | if (index > -1)
672 | return arr[index];
673 | else
674 | return false;
675 | };
676 | let moveOld = (item, node) => {
677 | let arr = map.get(item);
678 | if (!arr)
679 | return;
680 | let index = arr.findIndex(alone => alone === node);
681 | arr.splice(index, 1);
682 | };
683 | let maxIndexSoFar = { node: oldChildren[0], index: 0 };
684 | newDepArray.forEach((item, newIndex) => {
685 | let old = getOld(item);
686 | if (old) {
687 | if (old.index < maxIndexSoFar.index) {
688 | let next;
689 | if (newIndex > 0) {
690 | next = getNextSibling(newChildren[newIndex - 1]);
691 | }
692 | else {
693 | next = getNextSibling(maxIndexSoFar.node);
694 | }
695 | VNodeInsertBefore(container, old.node, next);
696 | // container.insertBefore(old.node.el!, next)
697 | }
698 | else {
699 | maxIndexSoFar = old;
700 | }
701 | newChildren[newIndex] = old.node;
702 | moveOld(item, old);
703 | }
704 | else {
705 | // let next = maxIndexSoFar.node.el!.nextSibling
706 | let next;
707 | if (newIndex > 0) {
708 | next = getNextSibling(newChildren[newIndex - 1]);
709 | }
710 | else {
711 | next = getNextSibling(maxIndexSoFar.node);
712 | }
713 | let newNode = newChildren[newIndex];
714 | mount(newNode, container, next);
715 | // maxIndexSoFar = { node: newNode, index: maxIndexSoFar.index + 1 }
716 | }
717 | });
718 | map.forEach(value => {
719 | value.forEach(item => {
720 | if (!item.used) {
721 | unmount(item.node);
722 | }
723 | });
724 | });
725 | }
726 | function patchArrayNode(oldVNode, newVNode, container) {
727 | const nextSibling = oldVNode.el.nextSibling;
728 | unmount(oldVNode);
729 | mount(newVNode, container, nextSibling);
730 | }
731 | /**
732 | * 将一个虚拟节点挂载到一个锚点前面
733 | */
734 | function VNodeInsertBefore(container, node, next) {
735 | if (node.flag === VNODE_TYPE.ELEMENT || node.flag === VNODE_TYPE.TEXT) {
736 | container.insertBefore(node.el, next);
737 | }
738 | else if (node.flag === VNODE_TYPE.COMPONENT) {
739 | container.insertBefore(node.root.el, next);
740 | }
741 | else if (node.flag === VNODE_TYPE.ARRAYNODE || node.flag === VNODE_TYPE.FRAGMENT) {
742 | let start = node.anchor;
743 | let nextToMove = start === null || start === void 0 ? void 0 : start.nextSibling;
744 | let end = node.el;
745 | while (start !== end) {
746 | container.insertBefore(start, next);
747 | start = nextToMove;
748 | nextToMove = start === null || start === void 0 ? void 0 : start.nextSibling;
749 | }
750 | container.insertBefore(end, next);
751 | }
752 | }
753 |
754 | function mount(vnode, container, anchor, app) {
755 | switch (vnode.flag) {
756 | case VNODE_TYPE.ELEMENT:
757 | mountElement(vnode, container, anchor, app);
758 | break;
759 | case VNODE_TYPE.TEXT:
760 | mountText(vnode, container, anchor);
761 | break;
762 | case VNODE_TYPE.FRAGMENT:
763 | mountFragment(vnode, container, anchor, app);
764 | break;
765 | case VNODE_TYPE.ARRAYNODE:
766 | mountArrayNode(vnode, container, anchor, app);
767 | break;
768 | case VNODE_TYPE.COMPONENT:
769 | mountComponent(vnode, container, anchor, app);
770 | break;
771 | case VNODE_TYPE.ALIVE:
772 | mountAlive(vnode, container, anchor, app);
773 | break;
774 | }
775 | }
776 | function unmount(vnode, container) {
777 | switch (vnode.flag) {
778 | case VNODE_TYPE.ELEMENT:
779 | unmountElement(vnode);
780 | break;
781 | case VNODE_TYPE.TEXT:
782 | unmountText(vnode);
783 | break;
784 | case VNODE_TYPE.FRAGMENT:
785 | unmountFragment(vnode);
786 | break;
787 | case VNODE_TYPE.ARRAYNODE:
788 | unmountArrayNode(vnode);
789 | break;
790 | case VNODE_TYPE.COMPONENT:
791 | unmountComponent(vnode);
792 | break;
793 | }
794 | }
795 | function mountChildren(children, container, anchor, app) {
796 | children.forEach(child => mount(child, container, anchor, app));
797 | }
798 | function mountText(vnode, container, anchor) {
799 | const el = document.createTextNode(vnode.children);
800 | vnode.el = el;
801 | container.insertBefore(el, anchor);
802 | }
803 | function unmountText(vnode, container) {
804 | vnode.el.remove();
805 | }
806 | function mountFragment(vnode, container, anchor, app) {
807 | const start = document.createTextNode('');
808 | const end = document.createTextNode('');
809 | vnode.anchor = start;
810 | vnode.el = end;
811 | container.insertBefore(start, anchor);
812 | mountChildren(vnode.children, container, anchor, app);
813 | container.insertBefore(end, anchor);
814 | }
815 | function unmountFragment(vnode, container) {
816 | const start = vnode.anchor;
817 | const end = vnode.el;
818 | let cur = start;
819 | while (cur && cur !== end) {
820 | let next = cur.nextSibling;
821 | cur.remove();
822 | cur = next;
823 | }
824 | end.remove();
825 | }
826 | function mountArrayNode(vnode, container, anchor, app) {
827 | const start = document.createTextNode('');
828 | const end = document.createTextNode('');
829 | vnode.anchor = start;
830 | vnode.el = end;
831 | container.insertBefore(start, anchor);
832 | mountChildren(vnode.children, container, anchor, app);
833 | container.insertBefore(end, anchor);
834 | }
835 | function unmountArrayNode(vnode, container, anchor) {
836 | const start = vnode.anchor;
837 | const end = vnode.el;
838 | let cur = start;
839 | while (cur && cur !== end) {
840 | let next = cur.nextSibling;
841 | cur.remove();
842 | cur = next;
843 | }
844 | end.remove();
845 | }
846 | function mountComponent(vNode, container, anchor, app) {
847 | const root = vNode.root;
848 | vNode.lifeStyleInstance.emit('readyMounted');
849 | mount(root, container, anchor, app);
850 | vNode.lifeStyleInstance.emit('mounted');
851 | }
852 | function unmountComponent(vNode, container) {
853 | vNode.lifeStyleInstance.emit('beforeUnMounted');
854 | unmount(vNode.root);
855 | vNode.lifeStyleInstance.emit('unMounted');
856 | }
857 | function mountAlive(vnode, container, anchor, app) {
858 | let firstVNode = watchVNode(vnode, (oldVNode, newVNode) => patch(oldVNode, newVNode, container, app));
859 | vnode.vnode = firstVNode;
860 | mount(firstVNode, container, anchor, app);
861 | }
862 |
863 | class Component {
864 | constructor() {
865 | this.utils = appUtils;
866 | }
867 | }
868 | function defineComponent(componentType) {
869 | return componentType;
870 | }
871 |
872 | class App {
873 | constructor(vNode, options) {
874 | this.rootVNode = vNode;
875 | this.options = options || {};
876 | this.pluginList = [];
877 | }
878 | mount(selector) {
879 | if (selector) {
880 | const el = (isString(selector) ? document.querySelector(selector) : selector) || document.body;
881 | mount(this.rootVNode, el, undefined, this);
882 | }
883 | else {
884 | mount(this.rootVNode, document.body, undefined, this);
885 | }
886 | // let container = document.createElement('div')
887 | // mount(this.rootVNode, container, undefined, this)
888 | // el?.replaceWith(...container.childNodes)
889 | }
890 | use(plugin) {
891 | let index = this.pluginList.indexOf(plugin);
892 | if (index > -1)
893 | return this;
894 | plugin.install({ utils: appUtils });
895 | return this;
896 | }
897 | }
898 | function createApp(vNode, options) {
899 | return new App(vNode, options);
900 | }
901 |
902 | exports.Activer = Activer;
903 | exports.Component = Component;
904 | exports.RefImpl = RefImpl;
905 | exports.VArray = ArraySymbol;
906 | exports.VComponent = VComponentSymbol;
907 | exports.VFragment = FragmentSymbol;
908 | exports.VText = TextSymbol;
909 | exports.Watcher = Watcher;
910 | exports.active = active;
911 | exports.createApp = createApp;
912 | exports.createVNode = renderApi;
913 | exports["default"] = App;
914 | exports.defineComponent = defineComponent;
915 | exports.defineState = defineState;
916 | exports.getActiver = getActiver;
917 | exports.h = renderApi;
918 | exports.mount = mount;
919 | exports.reactive = reactive;
920 | exports.ref = ref;
921 | exports.render = renderApi;
922 | exports.setActiver = setActiver;
923 | exports.state = state;
924 | exports.track = track;
925 | exports.trigger = trigger;
926 | exports.watch = watch;
927 | exports.watchProp = watchProp;
928 | exports.watchVNode = watchVNode;
929 |
930 | Object.defineProperty(exports, '__esModule', { value: true });
931 |
932 | }));
933 | //# sourceMappingURL=boundle.umd.js.map
934 |
--------------------------------------------------------------------------------
/lib/bundle.esm.js:
--------------------------------------------------------------------------------
1 | const TextSymbol = Symbol('Text');
2 |
3 | const FragmentSymbol = Symbol('Fragment');
4 |
5 | const VComponentSymbol = Symbol('VComponent');
6 |
7 | const ArraySymbol = Symbol('ArrayNode');
8 |
9 | const AliveSymbol = Symbol('Alive');
10 |
11 | /**
12 | * 虚拟dom节点类型枚举
13 | */
14 | var VNODE_TYPE;
15 | (function (VNODE_TYPE) {
16 | // 普通元素节点类型
17 | VNODE_TYPE[VNODE_TYPE["ELEMENT"] = 0] = "ELEMENT";
18 | // 文本节点类型
19 | VNODE_TYPE[VNODE_TYPE["TEXT"] = 1] = "TEXT";
20 | VNODE_TYPE[VNODE_TYPE["FRAGMENT"] = 2] = "FRAGMENT";
21 | VNODE_TYPE[VNODE_TYPE["COMPONENT"] = 3] = "COMPONENT";
22 | VNODE_TYPE[VNODE_TYPE["ARRAYNODE"] = 4] = "ARRAYNODE";
23 | VNODE_TYPE[VNODE_TYPE["ALIVE"] = 5] = "ALIVE";
24 | })(VNODE_TYPE || (VNODE_TYPE = {}));
25 |
26 | function isString(content) {
27 | return typeof content === 'string';
28 | }
29 | function isFunction(content) {
30 | return typeof content === 'function';
31 | }
32 | function isFragment(content) {
33 | return content === FragmentSymbol;
34 | }
35 | function isArrayNode(content) {
36 | return content === ArraySymbol;
37 | }
38 | function isText(content) {
39 | return content === TextSymbol;
40 | }
41 | function isActiver(content) {
42 | return isObject(content) && content.flag === 'activer';
43 | }
44 | function isVNode(content) {
45 | return isObject(content) && content.flag in VNODE_TYPE;
46 | }
47 | function isObjectExact(content) {
48 | return isObject(content) && content.constructor === Object;
49 | }
50 | function isOnEvent(str) {
51 | return /^on.+$/.test(str);
52 | }
53 | function isObject(content) {
54 | return typeof content === 'object' && content !== null;
55 | }
56 | function isArray(content) {
57 | return Array.isArray(content);
58 | }
59 |
60 | let updating = false;
61 | const watcherTask = [];
62 | function runUpdate(watcher) {
63 | let index = watcherTask.indexOf(watcher);
64 | if (!(index > -1))
65 | watcherTask.push(watcher);
66 | if (!updating) {
67 | updating = true;
68 | Promise.resolve()
69 | .then(() => {
70 | let watcher = undefined;
71 | while (watcher = watcherTask.shift()) {
72 | watcher.update();
73 | }
74 | }).finally(() => {
75 | updating = false;
76 | });
77 | }
78 | }
79 |
80 | // 目标对象到映射对象
81 | const targetMap = new WeakMap();
82 | // 全局变量watcher
83 | let activeWatcher = null;
84 | const REACTIVE = Symbol('reactive');
85 | /**
86 | * 实现响应式对象
87 | */
88 | function reactive(target) {
89 | if (target[REACTIVE])
90 | return target;
91 | let handler = {
92 | get(target, prop, receiver) {
93 | if (prop === REACTIVE)
94 | return true;
95 | const res = Reflect.get(target, prop, receiver);
96 | if (isObjectExact(res) && !isVNode(res)) {
97 | return reactive(res);
98 | }
99 | if (Array.isArray(res)) {
100 | track(target, prop);
101 | return reactiveArray(res, target, prop);
102 | }
103 | track(target, prop);
104 | return res;
105 | },
106 | set(target, prop, value, receiver) {
107 | const res = Reflect.set(target, prop, value, receiver);
108 | trigger(target, prop);
109 | return res;
110 | }
111 | };
112 | return new Proxy(target, handler);
113 | }
114 | /**
115 | * 设置响应式数组
116 | */
117 | function reactiveArray(targetArr, targetObj, Arrprop) {
118 | let handler = {
119 | get(target, prop, receiver) {
120 | const res = Reflect.get(target, prop, receiver);
121 | if (isObjectExact(res)) {
122 | return reactive(res);
123 | }
124 | return res;
125 | },
126 | set(target, prop, value, receiver) {
127 | const res = Reflect.set(target, prop, value, receiver);
128 | trigger(targetObj, Arrprop);
129 | return res;
130 | }
131 | };
132 | return new Proxy(targetArr, handler);
133 | }
134 | /**
135 | * 响应触发依赖
136 | */
137 | function trigger(target, prop) {
138 | let mapping = targetMap.get(target);
139 | if (!mapping)
140 | return;
141 | let mappingProp = mapping[prop];
142 | if (!mappingProp)
143 | return;
144 | // mappingProp.forEach(watcher => watcher.update(oldValue, newValue))
145 | mappingProp.forEach(watcher => {
146 | // 针对于对数组响应做特殊处理
147 | if (isArray(target[prop])) {
148 | watcher.nextDepArr = target[prop];
149 | }
150 | runUpdate(watcher);
151 | });
152 | }
153 | /**
154 | * 追踪绑定依赖
155 | */
156 | function track(target, prop) {
157 | if (!activeWatcher)
158 | return;
159 | let mapping = targetMap.get(target);
160 | if (!mapping)
161 | targetMap.set(target, mapping = {});
162 | let mappingProp = mapping[prop];
163 | if (!mappingProp)
164 | mappingProp = mapping[prop] = [];
165 | // 针对于对数组响应做特殊处理
166 | if (isArray(target[prop])) {
167 | if (activeWatcher.depArr) {
168 | activeWatcher.depArr = false;
169 | }
170 | else {
171 | if (activeWatcher.depArr === undefined) {
172 | activeWatcher.depArr = target[prop].slice();
173 | }
174 | }
175 | }
176 | mappingProp.push(activeWatcher);
177 | }
178 | // 设置全局变量
179 | function setActiver(fn) {
180 | activeWatcher = fn;
181 | }
182 | // 设置全局变量
183 | function getActiver() {
184 | return activeWatcher;
185 | }
186 |
187 | class RefImpl {
188 | constructor(value) {
189 | this._value = value;
190 | this._target = { value: this._value };
191 | }
192 | get value() {
193 | track(this._target, 'value');
194 | return this._value;
195 | }
196 | set value(value) {
197 | this._value = value;
198 | this._target.value = this._value;
199 | trigger(this._target, 'value');
200 | }
201 | }
202 | function ref(value) {
203 | return new RefImpl(value);
204 | }
205 |
206 | function state(value) {
207 | return ref(value);
208 | }
209 | function defineState(target) {
210 | return reactive(target);
211 | }
212 |
213 | /**
214 | * 响应式对象
215 | */
216 | class Activer {
217 | constructor(fn) {
218 | this.flag = 'activer';
219 | this.callback = fn;
220 | }
221 | get value() {
222 | return this.callback();
223 | }
224 | }
225 | /**
226 | * 外置函数
227 | */
228 | function active(fn) {
229 | return new Activer(fn);
230 | }
231 |
232 | class ComponentLifeCycle {
233 | constructor(createdList = [], beforeMountedList = [], readyMountedList = [], mountedList = [], beforeUnMountedList = [], unMountedList = []) {
234 | this.createdList = createdList;
235 | this.beforeMountedList = beforeMountedList;
236 | this.readyMountedList = readyMountedList;
237 | this.mountedList = mountedList;
238 | this.beforeUnMountedList = beforeUnMountedList;
239 | this.unMountedList = unMountedList;
240 | // this.createdList = []
241 | // this.beforeMountedList = []
242 | // this.readyMountedList = []
243 | // this.mountedList = []
244 | // this.beforeUnMountedList = []
245 | // this.unMountedList = []
246 | }
247 | created(fn) {
248 | this.createdList.push(fn);
249 | }
250 | beforeMounted(fn) {
251 | this.beforeMountedList.push(fn);
252 | }
253 | readyMounted(fn) {
254 | this.readyMountedList.push(fn);
255 | }
256 | mounted(fn) {
257 | this.mountedList.push(fn);
258 | }
259 | beforeUnMounted(fn) {
260 | this.beforeUnMountedList.push(fn);
261 | }
262 | unMounted(fn) {
263 | this.unMountedList.push(fn);
264 | }
265 | emit(lifeName) {
266 | this[`${lifeName}List`].forEach(fn => fn());
267 | }
268 | }
269 | // 生成类组件生命周期实例
270 | function createClassComponentLife(component) {
271 | let lifeNames = ['created', 'beforeMounted', 'readyMounted', 'mounted', 'beforeUnMounted', 'unMounted'];
272 | let lifeCycle = new ComponentLifeCycle();
273 | component.life = lifeCycle;
274 | lifeNames.forEach(lifeName => {
275 | let fn = component[lifeName];
276 | if (!fn)
277 | return;
278 | lifeCycle[lifeName](fn.bind(component));
279 | });
280 | return lifeCycle;
281 | }
282 |
283 | // 给插件提供的能力
284 | const appUtils = {
285 | state,
286 | defineState,
287 | h: renderApi,
288 | watch
289 | };
290 |
291 | /**
292 | * 函数转化为activer转化为VAlive
293 | * activer转化为VAlive
294 | * string转化为VText
295 | * 数组转化为VArray
296 | * 其他转化为VText
297 | */
298 | function createVNode(originVNode) {
299 | if (isVNode(originVNode))
300 | return originVNode;
301 | if (isFunction(originVNode))
302 | return renderAlive(active(originVNode));
303 | else if (isActiver(originVNode))
304 | return renderAlive(originVNode);
305 | else if (isString(originVNode))
306 | return renderText(originVNode);
307 | else if (isArray(originVNode))
308 | return renderArrayNode(originVNode.map(item => createVNode(item)));
309 | else {
310 | // todo
311 | let value = originVNode;
312 | let retText;
313 | if (!value && typeof value !== 'number')
314 | retText = renderText('');
315 | else
316 | retText = renderText(String(value));
317 | return retText;
318 | }
319 | }
320 | /**
321 | * text(不需要props)、fragment(不需要props)、element、component为显性创建
322 | * array(不需要props)、alive(不需要props)为隐形创建
323 | */
324 | function renderApi(type, props, children) {
325 | return render(type, props || undefined, children);
326 | }
327 | function render(type, originProps, originChildren) {
328 | // text的children比较特殊先处理
329 | if (isText(type)) {
330 | return renderText(String(originChildren));
331 | }
332 | // 预处理 处理为单个的children
333 | let originChildrenList = [];
334 | if (isArray(originChildren))
335 | originChildrenList.push(...originChildren);
336 | else
337 | originChildrenList.push(originChildren);
338 | // 创建VNode列表
339 | let vNodeChildren = originChildrenList.map(originChild => createVNode(originChild));
340 | // 属性预处理
341 | let props = originProps || {};
342 | handleProps(props);
343 | if (isString(type))
344 | return renderElement(type, props, vNodeChildren);
345 | if (isFragment(type))
346 | return renderFragment(vNodeChildren);
347 | if (isArrayNode(type))
348 | return renderArrayNode(vNodeChildren);
349 | if (isFunction(type))
350 | return renderComponent(type, props, vNodeChildren);
351 | throw '传入参数不合法';
352 | }
353 | /**
354 | * 对属性进行预处理
355 | */
356 | function handleProps(originProps) {
357 | for (const prop in originProps) {
358 | // 以on开头的事件不需要处理
359 | if (!isOnEvent(prop) &&
360 | isFunction(originProps[prop])) {
361 | // 如不为on且为函数则判断为响应式
362 | originProps[prop] = active(originProps[prop]);
363 | }
364 | }
365 | return originProps;
366 | }
367 | function renderText(text) {
368 | return {
369 | type: TextSymbol,
370 | flag: VNODE_TYPE.TEXT,
371 | children: text
372 | };
373 | }
374 | function renderElement(tag, props, children) {
375 | return {
376 | type: tag,
377 | flag: VNODE_TYPE.ELEMENT,
378 | props,
379 | children
380 | };
381 | }
382 | function renderFragment(children) {
383 | return {
384 | type: FragmentSymbol,
385 | flag: VNODE_TYPE.FRAGMENT,
386 | children
387 | };
388 | }
389 | function renderArrayNode(children) {
390 | return {
391 | type: ArraySymbol,
392 | flag: VNODE_TYPE.ARRAYNODE,
393 | children
394 | };
395 | }
396 | function createComponentProps(props) {
397 | let componentProps = {};
398 | for (const prop in props) {
399 | let curProp = props[prop];
400 | if (isActiver(curProp)) {
401 | Object.defineProperty(componentProps, prop, {
402 | get() {
403 | return curProp.value;
404 | }
405 | });
406 | }
407 | else {
408 | componentProps[prop] = curProp;
409 | }
410 | }
411 | return componentProps;
412 | }
413 | // 渲染一个活跃的节点
414 | function renderAlive(activer) {
415 | return {
416 | type: AliveSymbol,
417 | flag: VNODE_TYPE.ALIVE,
418 | activer
419 | };
420 | }
421 | // 判断是普通函数还是构造函数
422 | function renderComponent(component, props, children) {
423 | let componentProps = createComponentProps(props);
424 | if (component.prototype &&
425 | component.prototype.render &&
426 | isFunction(component.prototype.render)) {
427 | let ClassComponent = component;
428 | let result = new ClassComponent(componentProps, children);
429 | result.props = componentProps;
430 | result.children = children;
431 | let lifeCycle = createClassComponentLife(result);
432 | let vn = result.render(renderApi);
433 | lifeCycle.emit('created');
434 | let vc = {
435 | type: VComponentSymbol,
436 | root: createVNode(vn),
437 | props: componentProps,
438 | children: children,
439 | flag: VNODE_TYPE.COMPONENT,
440 | lifeStyleInstance: lifeCycle
441 | };
442 | lifeCycle.emit('beforeMounted');
443 | return vc;
444 | }
445 | else {
446 | let FunctionComponent = component;
447 | let lifeCycle = new ComponentLifeCycle();
448 | let vn = FunctionComponent({ props: componentProps, children: children, life: lifeCycle, utils: appUtils });
449 | lifeCycle.emit('created');
450 | let vc = {
451 | type: VComponentSymbol,
452 | root: createVNode(vn),
453 | props: componentProps,
454 | children: children,
455 | flag: VNODE_TYPE.COMPONENT,
456 | lifeStyleInstance: lifeCycle
457 | };
458 | lifeCycle.emit('beforeMounted');
459 | return vc;
460 | }
461 | }
462 |
463 | /**
464 | * 观察者
465 | * 观察数据的变化
466 | */
467 | class Watcher {
468 | constructor(activeProps, callback) {
469 | setActiver(this);
470 | this.value = activeProps.value;
471 | this.callback = callback;
472 | this.activeProps = activeProps;
473 | setActiver(null);
474 | }
475 | update() {
476 | var _a;
477 | let newValue = this.activeProps.value;
478 | let oldValue = this.value;
479 | this.value = newValue;
480 | let meta = { targetPropOldValue: this.depArr, targetPropnewValue: this.nextDepArr };
481 | this.callback(oldValue, newValue, meta);
482 | this.depArr = (_a = this.nextDepArr) === null || _a === void 0 ? void 0 : _a.slice();
483 | }
484 | }
485 | /**
486 | * 监控自定义响应式属性
487 | */
488 | function watch(activeProps, callback) {
489 | if (!isActiver(activeProps))
490 | activeProps = active(activeProps);
491 | return new Watcher(activeProps, function (oldValue, newValue) {
492 | callback(oldValue, newValue);
493 | });
494 | }
495 | /**
496 | * 监控可变状态dom
497 | */
498 | function watchVNode(activeVNode, callback) {
499 | let watcher = new Watcher(activeVNode.activer, function (oldValue, newValue, meta) {
500 | const oldVNode = oldValue;
501 | const newVNode = createVNode(newValue);
502 | // 对于数组节点后期需要记录它的响应式数组用于节点更新
503 | if (oldVNode.flag === VNODE_TYPE.ARRAYNODE) {
504 | oldVNode.depArray = meta === null || meta === void 0 ? void 0 : meta.targetPropOldValue;
505 | }
506 | // 对于数组节点后期需要记录它的响应式数组用于节点更新
507 | if (newVNode.flag === VNODE_TYPE.ARRAYNODE) {
508 | newVNode.depArray = meta === null || meta === void 0 ? void 0 : meta.targetPropnewValue;
509 | }
510 | callback(oldVNode, newVNode);
511 | watcher.value = newVNode;
512 | activeVNode.vnode = newVNode;
513 | });
514 | return watcher.value = createVNode(watcher.value);
515 | }
516 | /**
517 | * 监控可变dom的prop
518 | */
519 | function watchProp(activeProp, callback) {
520 | return new Watcher(activeProp, callback).value;
521 | }
522 |
523 | function mountElement(vnode, container, anchor, app) {
524 | const el = document.createElement(vnode.type);
525 | vnode.el = el;
526 | mountElementProps(vnode);
527 | mountChildren(vnode.children, el, undefined, app);
528 | container.insertBefore(el, anchor);
529 | }
530 | function unmountElement(vnode, container) {
531 | vnode.el.remove();
532 | }
533 | function mountElementProps(vnode) {
534 | let el = vnode.el;
535 | let props = vnode.props;
536 | // 处理标签属性
537 | for (let prop in props) {
538 | let value = props[prop];
539 | if (isActiver(value)) {
540 | let firstValue = watchProp(value, (oldValue, newValue) => patchElementProp(oldValue, newValue, el, prop));
541 | setElementProp(el, prop, firstValue);
542 | }
543 | else {
544 | setElementProp(el, prop, value);
545 | }
546 | }
547 | }
548 | /**
549 | * 处理单个dom属性
550 | */
551 | function setElementProp(el, prop, value) {
552 | if (isOnEvent(prop) && isFunction(value)) {
553 | let pattern = /^on(.+)$/;
554 | let result = prop.match(pattern);
555 | result && el.addEventListener(result[1].toLocaleLowerCase(), value.bind(el));
556 | return;
557 | }
558 | switch (prop) {
559 | case 'className':
560 | el.className = String(value);
561 | break;
562 | case 'style':
563 | if (isObject(value)) {
564 | value = mergeStyle(value);
565 | }
566 | default:
567 | el.setAttribute(prop, value);
568 | }
569 | }
570 | /**
571 | * 将对象形式的style转化为字符串
572 | */
573 | function mergeStyle(style) {
574 | let styleStringList = [];
575 | for (let cssAttr in style) {
576 | styleStringList.push(`${cssAttr}:${style[cssAttr]};`);
577 | }
578 | return styleStringList.join('');
579 | }
580 |
581 | function isSameVNode(oldVNode, newVNode) {
582 | return oldVNode.flag === newVNode.flag;
583 | }
584 | // todo
585 | function getNextSibling(vNode) {
586 | switch (vNode.flag) {
587 | case VNODE_TYPE.TEXT:
588 | case VNODE_TYPE.ELEMENT:
589 | case VNODE_TYPE.ARRAYNODE:
590 | case VNODE_TYPE.FRAGMENT:
591 | return vNode.el.nextSibling;
592 | case VNODE_TYPE.COMPONENT:
593 | return vNode.root.el.nextSibling;
594 | case VNODE_TYPE.ALIVE:
595 | return getNextSibling(vNode.vnode);
596 | }
597 | }
598 | function patch(oldVNode, newVNode, container, app) {
599 | // 如果两个节点引用一样不需要判断
600 | if (oldVNode === newVNode)
601 | return;
602 | // 这里在判断相同类型的节点后可以做进一步优化
603 | if (isSameVNode(oldVNode, newVNode)) {
604 | let flag = oldVNode.flag = newVNode.flag;
605 | if (flag === VNODE_TYPE.TEXT) {
606 | patchText(oldVNode, newVNode);
607 | }
608 | else if (flag === VNODE_TYPE.ARRAYNODE) {
609 | if (app === null || app === void 0 ? void 0 : app.options.arrayDiff) {
610 | patchArrayNodeT(oldVNode, newVNode, container);
611 | }
612 | else {
613 | patchArrayNode(oldVNode, newVNode, container);
614 | }
615 | }
616 | else {
617 | // const nextSibling = oldVNode?.el?.nextSibling
618 | const nextSibling = getNextSibling(oldVNode);
619 | unmount(oldVNode);
620 | mount(newVNode, container, nextSibling);
621 | }
622 | }
623 | else {
624 | // const nextSibling = oldVNode?.el?.nextSibling
625 | const nextSibling = getNextSibling(oldVNode);
626 | unmount(oldVNode);
627 | mount(newVNode, container, nextSibling);
628 | }
629 | }
630 | function patchText(oldVNode, newVNode, container) {
631 | oldVNode.el.nodeValue = newVNode.children;
632 | newVNode.el = oldVNode.el;
633 | }
634 | function patchElementProp(oldValue, newValue, el, prop) {
635 | setElementProp(el, prop, newValue);
636 | }
637 | function patchArrayNodeT(oldVNode, newVNode, container) {
638 | if (!oldVNode.depArray) {
639 | patchArrayNode(oldVNode, newVNode, container);
640 | return;
641 | }
642 | const oldDepArray = oldVNode.depArray;
643 | const newDepArray = newVNode.depArray;
644 | const oldChildren = oldVNode.children;
645 | const newChildren = newVNode.children;
646 | if (!oldDepArray.length || !newDepArray.length) {
647 | patchArrayNode(oldVNode, newVNode, container);
648 | return;
649 | }
650 | newVNode.anchor = oldVNode.anchor;
651 | newVNode.el = oldVNode.el;
652 | // 为映射做初始化
653 | let map = new Map();
654 | oldDepArray.forEach((item, index) => {
655 | let arr = map.get(item);
656 | if (!arr)
657 | map.set(item, arr = []);
658 | arr.push({ node: oldChildren[index], index, used: false });
659 | });
660 | let getOld = (item) => {
661 | let arr = map.get(item);
662 | if (!arr)
663 | return false;
664 | let index = arr.findIndex(alone => !alone.used);
665 | if (index > -1)
666 | return arr[index];
667 | else
668 | return false;
669 | };
670 | let moveOld = (item, node) => {
671 | let arr = map.get(item);
672 | if (!arr)
673 | return;
674 | let index = arr.findIndex(alone => alone === node);
675 | arr.splice(index, 1);
676 | };
677 | let maxIndexSoFar = { node: oldChildren[0], index: 0 };
678 | newDepArray.forEach((item, newIndex) => {
679 | let old = getOld(item);
680 | if (old) {
681 | if (old.index < maxIndexSoFar.index) {
682 | let next;
683 | if (newIndex > 0) {
684 | next = getNextSibling(newChildren[newIndex - 1]);
685 | }
686 | else {
687 | next = getNextSibling(maxIndexSoFar.node);
688 | }
689 | VNodeInsertBefore(container, old.node, next);
690 | // container.insertBefore(old.node.el!, next)
691 | }
692 | else {
693 | maxIndexSoFar = old;
694 | }
695 | newChildren[newIndex] = old.node;
696 | moveOld(item, old);
697 | }
698 | else {
699 | // let next = maxIndexSoFar.node.el!.nextSibling
700 | let next;
701 | if (newIndex > 0) {
702 | next = getNextSibling(newChildren[newIndex - 1]);
703 | }
704 | else {
705 | next = getNextSibling(maxIndexSoFar.node);
706 | }
707 | let newNode = newChildren[newIndex];
708 | mount(newNode, container, next);
709 | // maxIndexSoFar = { node: newNode, index: maxIndexSoFar.index + 1 }
710 | }
711 | });
712 | map.forEach(value => {
713 | value.forEach(item => {
714 | if (!item.used) {
715 | unmount(item.node);
716 | }
717 | });
718 | });
719 | }
720 | function patchArrayNode(oldVNode, newVNode, container) {
721 | const nextSibling = oldVNode.el.nextSibling;
722 | unmount(oldVNode);
723 | mount(newVNode, container, nextSibling);
724 | }
725 | /**
726 | * 将一个虚拟节点挂载到一个锚点前面
727 | */
728 | function VNodeInsertBefore(container, node, next) {
729 | if (node.flag === VNODE_TYPE.ELEMENT || node.flag === VNODE_TYPE.TEXT) {
730 | container.insertBefore(node.el, next);
731 | }
732 | else if (node.flag === VNODE_TYPE.COMPONENT) {
733 | container.insertBefore(node.root.el, next);
734 | }
735 | else if (node.flag === VNODE_TYPE.ARRAYNODE || node.flag === VNODE_TYPE.FRAGMENT) {
736 | let start = node.anchor;
737 | let nextToMove = start === null || start === void 0 ? void 0 : start.nextSibling;
738 | let end = node.el;
739 | while (start !== end) {
740 | container.insertBefore(start, next);
741 | start = nextToMove;
742 | nextToMove = start === null || start === void 0 ? void 0 : start.nextSibling;
743 | }
744 | container.insertBefore(end, next);
745 | }
746 | }
747 |
748 | function mount(vnode, container, anchor, app) {
749 | switch (vnode.flag) {
750 | case VNODE_TYPE.ELEMENT:
751 | mountElement(vnode, container, anchor, app);
752 | break;
753 | case VNODE_TYPE.TEXT:
754 | mountText(vnode, container, anchor);
755 | break;
756 | case VNODE_TYPE.FRAGMENT:
757 | mountFragment(vnode, container, anchor, app);
758 | break;
759 | case VNODE_TYPE.ARRAYNODE:
760 | mountArrayNode(vnode, container, anchor, app);
761 | break;
762 | case VNODE_TYPE.COMPONENT:
763 | mountComponent(vnode, container, anchor, app);
764 | break;
765 | case VNODE_TYPE.ALIVE:
766 | mountAlive(vnode, container, anchor, app);
767 | break;
768 | }
769 | }
770 | function unmount(vnode, container) {
771 | switch (vnode.flag) {
772 | case VNODE_TYPE.ELEMENT:
773 | unmountElement(vnode);
774 | break;
775 | case VNODE_TYPE.TEXT:
776 | unmountText(vnode);
777 | break;
778 | case VNODE_TYPE.FRAGMENT:
779 | unmountFragment(vnode);
780 | break;
781 | case VNODE_TYPE.ARRAYNODE:
782 | unmountArrayNode(vnode);
783 | break;
784 | case VNODE_TYPE.COMPONENT:
785 | unmountComponent(vnode);
786 | break;
787 | }
788 | }
789 | function mountChildren(children, container, anchor, app) {
790 | children.forEach(child => mount(child, container, anchor, app));
791 | }
792 | function mountText(vnode, container, anchor) {
793 | const el = document.createTextNode(vnode.children);
794 | vnode.el = el;
795 | container.insertBefore(el, anchor);
796 | }
797 | function unmountText(vnode, container) {
798 | vnode.el.remove();
799 | }
800 | function mountFragment(vnode, container, anchor, app) {
801 | const start = document.createTextNode('');
802 | const end = document.createTextNode('');
803 | vnode.anchor = start;
804 | vnode.el = end;
805 | container.insertBefore(start, anchor);
806 | mountChildren(vnode.children, container, anchor, app);
807 | container.insertBefore(end, anchor);
808 | }
809 | function unmountFragment(vnode, container) {
810 | const start = vnode.anchor;
811 | const end = vnode.el;
812 | let cur = start;
813 | while (cur && cur !== end) {
814 | let next = cur.nextSibling;
815 | cur.remove();
816 | cur = next;
817 | }
818 | end.remove();
819 | }
820 | function mountArrayNode(vnode, container, anchor, app) {
821 | const start = document.createTextNode('');
822 | const end = document.createTextNode('');
823 | vnode.anchor = start;
824 | vnode.el = end;
825 | container.insertBefore(start, anchor);
826 | mountChildren(vnode.children, container, anchor, app);
827 | container.insertBefore(end, anchor);
828 | }
829 | function unmountArrayNode(vnode, container, anchor) {
830 | const start = vnode.anchor;
831 | const end = vnode.el;
832 | let cur = start;
833 | while (cur && cur !== end) {
834 | let next = cur.nextSibling;
835 | cur.remove();
836 | cur = next;
837 | }
838 | end.remove();
839 | }
840 | function mountComponent(vNode, container, anchor, app) {
841 | const root = vNode.root;
842 | vNode.lifeStyleInstance.emit('readyMounted');
843 | mount(root, container, anchor, app);
844 | vNode.lifeStyleInstance.emit('mounted');
845 | }
846 | function unmountComponent(vNode, container) {
847 | vNode.lifeStyleInstance.emit('beforeUnMounted');
848 | unmount(vNode.root);
849 | vNode.lifeStyleInstance.emit('unMounted');
850 | }
851 | function mountAlive(vnode, container, anchor, app) {
852 | let firstVNode = watchVNode(vnode, (oldVNode, newVNode) => patch(oldVNode, newVNode, container, app));
853 | vnode.vnode = firstVNode;
854 | mount(firstVNode, container, anchor, app);
855 | }
856 |
857 | class Component {
858 | constructor() {
859 | this.utils = appUtils;
860 | }
861 | }
862 | function defineComponent(componentType) {
863 | return componentType;
864 | }
865 |
866 | class App {
867 | constructor(vNode, options) {
868 | this.rootVNode = vNode;
869 | this.options = options || {};
870 | this.pluginList = [];
871 | }
872 | mount(selector) {
873 | if (selector) {
874 | const el = (isString(selector) ? document.querySelector(selector) : selector) || document.body;
875 | mount(this.rootVNode, el, undefined, this);
876 | }
877 | else {
878 | mount(this.rootVNode, document.body, undefined, this);
879 | }
880 | // let container = document.createElement('div')
881 | // mount(this.rootVNode, container, undefined, this)
882 | // el?.replaceWith(...container.childNodes)
883 | }
884 | use(plugin) {
885 | let index = this.pluginList.indexOf(plugin);
886 | if (index > -1)
887 | return this;
888 | plugin.install({ utils: appUtils });
889 | return this;
890 | }
891 | }
892 | function createApp(vNode, options) {
893 | return new App(vNode, options);
894 | }
895 |
896 | export { Activer, Component, RefImpl, ArraySymbol as VArray, VComponentSymbol as VComponent, FragmentSymbol as VFragment, TextSymbol as VText, Watcher, active, createApp, renderApi as createVNode, App as default, defineComponent, defineState, getActiver, renderApi as h, mount, reactive, ref, renderApi as render, setActiver, state, track, trigger, watch, watchProp, watchVNode };
897 | //# sourceMappingURL=bundle.esm.js.map
898 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vactapp",
3 | "version": "0.9.2",
4 | "main": "lib/boundle.js",
5 | "types": "index.d.ts",
6 | "license": "MIT",
7 | "author": "烟云雾沧海",
8 | "description": "前端响应式新框架",
9 | "scripts": {
10 | "dev": "rollup -c --watch",
11 | "test": "jest --passWithNoTests",
12 | "build": "cross-env NODE_ENV=production rollup -c"
13 | },
14 | "devDependencies": {
15 | "@types/jest": "^28.1.7",
16 | "@types/node": "^18.0.0",
17 | "cross-env": "^7.0.3",
18 | "jest": "^28.1.3",
19 | "rollup-plugin-dts": "^4.2.2",
20 | "rollup-plugin-livereload": "^2.0.5",
21 | "rollup-plugin-node-polyfills": "^0.2.1",
22 | "rollup-plugin-serve": "^1.1.0",
23 | "rollup-plugin-sourcemaps": "^0.6.3",
24 | "rollup-plugin-terser": "^7.0.2",
25 | "rollup-plugin-typescript": "^1.0.1",
26 | "ts-jest": "^28.0.8",
27 | "tslib": "^2.4.0"
28 | },
29 | "repository": "https://github.com/yanyunwu/vact",
30 | "keywords": [
31 | "前端框架",
32 | "响应式",
33 | "响应式前端框架"
34 | ],
35 | "files": [
36 | "lib",
37 | "src",
38 | "index.d.ts",
39 | "index.ts",
40 | "LICENSE",
41 | "package.json",
42 | "README.md",
43 | "tsconfig.json"
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | // rollup.config.js
2 | import livereload from 'rollup-plugin-livereload'
3 | import serve from 'rollup-plugin-serve'
4 | /** 打包nodejs内置工具模块 */
5 | import nodePolyfills from 'rollup-plugin-node-polyfills';
6 | /** 支持ts打包 */
7 | import typescript from "rollup-plugin-typescript";
8 | import dts from "rollup-plugin-dts";
9 | /** 报错追源 */
10 | import sourceMaps from "rollup-plugin-sourcemaps";
11 | /** 压缩打包 */
12 | import { terser } from 'rollup-plugin-terser';
13 |
14 | const isDev = process.env.NODE_ENV !== 'production';
15 |
16 | const devConfig = {
17 | input: "index.ts", // 入口文件
18 | output: [
19 | {
20 | file: "lib/boundle.js", // 必须
21 | format: "cjs", // 必须
22 | sourcemap: true
23 | },
24 | {
25 | file: "examples/boundle/boundle.js", // 必须
26 | format: 'umd',
27 | name: 'Vact'
28 | }
29 | ],
30 |
31 | plugins: [
32 | nodePolyfills(),
33 | livereload(),
34 | serve({
35 | // open: true,
36 | port: 3000,
37 | contentBase: './examples'
38 | }),
39 | typescript({
40 | exclude: "node_modules/**",
41 | typescript: require("typescript")
42 | }),
43 | sourceMaps(),
44 | ]
45 |
46 | };
47 |
48 | const proConfig = {
49 | input: "index.ts", // 入口文件
50 | output: [
51 | {
52 | file: "lib/boundle.js", // 必须
53 | format: "cjs", // 必须
54 | sourcemap: true
55 | },
56 | {
57 | file: "lib/boundle.umd.js", // 必须
58 | format: 'umd',
59 | name: 'Vact',
60 | sourcemap: true
61 | },
62 | {
63 | file: "lib/bundle.esm.js",
64 | format: "es",
65 | sourcemap: true
66 | }
67 | ],
68 |
69 | plugins: [
70 | nodePolyfills(),
71 | typescript({
72 | exclude: "node_modules/**",
73 | typescript: require("typescript")
74 | }),
75 | sourceMaps(),
76 | // terser()
77 | ]
78 |
79 | };
80 |
81 | export default [
82 | isDev ? devConfig : proConfig,
83 | {
84 | // 生成 .d.ts 类型声明文件
85 | input: 'index.ts',
86 | output: {
87 | file: "index.d.ts",
88 | format: 'es',
89 | },
90 | plugins: [
91 | dts()
92 | ]
93 | }
94 | ]
--------------------------------------------------------------------------------
/src/__test__/index.ts:
--------------------------------------------------------------------------------
1 |
2 | class C {
3 | list: Array
4 | constructor() {
5 | this.list = []
6 | }
7 | }
8 |
9 | abstract class B extends C{
10 | say() {
11 | console.log(this.list)
12 | }
13 | }
14 |
15 |
16 | class A extends B {
17 |
18 | }
19 |
20 |
21 | let v = new A()
22 |
23 |
24 | v.say()
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/app.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "./mount/mount";
2 | import { VNode } from "./vnode";
3 | import {isString} from "./utils";
4 | import { AppPlugin, appUtils } from './plugin'
5 |
6 | export interface Options {
7 | arrayDiff?: boolean
8 | }
9 |
10 | export class App {
11 | rootVNode: VNode
12 | options: Options
13 |
14 | private pluginList: Array
15 |
16 | constructor(vNode: VNode, options?: Options) {
17 | this.rootVNode = vNode
18 | this.options = options || {}
19 | this.pluginList = []
20 | }
21 |
22 | mount(selector?: string | HTMLElement): void {
23 | if(selector) {
24 | const el = (isString(selector) ? document.querySelector(selector) : selector) || document.body
25 | mount(this.rootVNode, el as HTMLElement, undefined, this)
26 | }else {
27 | mount(this.rootVNode, document.body, undefined, this)
28 | }
29 |
30 | // let container = document.createElement('div')
31 | // mount(this.rootVNode, container, undefined, this)
32 | // el?.replaceWith(...container.childNodes)
33 | }
34 |
35 | use(plugin: AppPlugin) {
36 | let index = this.pluginList.indexOf(plugin)
37 | if(index > -1) return this
38 |
39 | plugin.install({utils: appUtils})
40 | return this
41 | }
42 | }
43 |
44 | export function createApp(vNode: VNode, options?: Options): App {
45 | return new App(vNode, options)
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/src/component.ts:
--------------------------------------------------------------------------------
1 | import { H } from './render'
2 | import { VNode, VNodeProps, OriginVNode, FunctionComponentType } from "./vnode"
3 | import {appUtils, AppUtils} from "./plugin";
4 | import {ComponentLifeCycle} from "./lifeCycle";
5 |
6 |
7 | /**
8 | * 根类组件用于类型提示
9 | * 抽象类
10 | */
11 |
12 | // todo
13 | export interface ComponentInstance {
14 | props?: VNodeProps
15 | children?: Array
16 | utils: AppUtils
17 | life?: ComponentLifeCycle
18 | render(h?: H): OriginVNode
19 |
20 | /** 组件示例创建之后触发 */
21 | created?(): void
22 |
23 | /** 组件示例在渲染之前触发(DOM还没有生成) */
24 | beforeMounted?(): void
25 |
26 | /** dom已经生成到js内存但还没挂载 */
27 | readyMounted?(): void
28 |
29 | /** dom挂载到了页面上*/
30 | mounted?(): void
31 |
32 | /** 卸载之前触发 */
33 | beforeUnMounted?():void
34 |
35 | /** 卸载之后触发 */
36 | unMounted?():void
37 | }
38 |
39 | export abstract class Component implements ComponentInstance{
40 | // 组件属性
41 | abstract props?: VNodeProps
42 | // 组件子元素
43 | abstract children?: Array
44 |
45 | abstract render(h?: H): VNode
46 |
47 | utils: AppUtils = appUtils
48 | abstract life?: ComponentLifeCycle
49 |
50 | /** 组件示例创建之后触发 */
51 | abstract created?(): void
52 |
53 | /** 组件示例在渲染之前触发(DOM还没有生成) */
54 | abstract beforeMounted?(): void
55 |
56 | /** dom已经生成到js内存但还没挂载 */
57 | abstract readyMounted?(): void
58 |
59 | /** dom挂载到了页面上*/
60 | abstract mounted?(): void
61 |
62 | /** 卸载之前触发 */
63 | abstract beforeUnMounted?():void
64 |
65 | /** 卸载之后触发 */
66 | abstract unMounted?():void
67 | }
68 |
69 | export function defineComponent(componentType: FunctionComponentType): FunctionComponentType {
70 | return componentType
71 | }
72 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { mount } from "./mount/mount";
2 | import { renderApi } from './render'
3 | import {
4 | TextSymbol,
5 | FragmentSymbol,
6 | ArraySymbol,
7 | VComponentSymbol
8 | } from './vnode'
9 | import { Component, defineComponent } from './component'
10 | import { App, createApp } from './app'
11 |
12 | export * from './reactive'
13 | export {
14 | mount,
15 | renderApi as render,
16 | renderApi as h,
17 | Component,
18 | defineComponent
19 | }
20 | export {
21 | TextSymbol as VText,
22 | FragmentSymbol as VFragment,
23 | ArraySymbol as VArray,
24 | VComponentSymbol as VComponent,
25 | }
26 | export { renderApi as createVNode }
27 | export { createApp }
28 | export default App
29 |
--------------------------------------------------------------------------------
/src/lifeCycle.ts:
--------------------------------------------------------------------------------
1 | import { ComponentInstance } from './component'
2 |
3 | /**
4 | * 主要实现关于组件的生命周期
5 | */
6 |
7 |
8 | export interface ComponentLifeCycleInstance {
9 |
10 | /** 组件示例创建之后触发 */
11 | created(fn: () => void): void
12 |
13 | /** 组件示例在渲染之前触发(DOM还没有生成) */
14 | beforeMounted(fn: () => void): void
15 |
16 | /** dom已经生成到js内存但还没挂载 */
17 | readyMounted(fn: () => void): void
18 |
19 | /** dom挂载到了页面上*/
20 | mounted(fn: () => void): void
21 |
22 | /** 卸载之前触发 */
23 | beforeUnMounted(fn: () => void):void
24 |
25 | /** 卸载之后触发 */
26 | unMounted(fn: () => void):void
27 | }
28 |
29 | type LifeCycleNames = T extends string ? `${T}List` : never
30 | type GetCycleName = T extends `${infer U}List` ? U : never
31 |
32 |
33 |
34 | export class ComponentLifeCycle implements ComponentLifeCycleInstance {
35 |
36 |
37 | constructor(
38 | private readonly createdList: Array<() => void> = [],
39 | private readonly beforeMountedList: Array<() => void> = [],
40 | private readonly readyMountedList: Array<() => void> = [],
41 | private readonly mountedList: Array<() => void> = [],
42 | private readonly beforeUnMountedList: Array<() => void> = [],
43 | private readonly unMountedList: Array<() => void> = []
44 | ) {
45 | // this.createdList = []
46 | // this.beforeMountedList = []
47 | // this.readyMountedList = []
48 | // this.mountedList = []
49 | // this.beforeUnMountedList = []
50 | // this.unMountedList = []
51 | }
52 |
53 | created(fn: () => void): void {
54 | this.createdList.push(fn)
55 | }
56 |
57 | beforeMounted(fn: () => void): void {
58 | this.beforeMountedList.push(fn)
59 | }
60 |
61 | readyMounted(fn: () => void): void {
62 | this.readyMountedList.push(fn)
63 | }
64 |
65 | mounted(fn: () => void): void {
66 | this.mountedList.push(fn)
67 | }
68 |
69 | beforeUnMounted(fn: () => void): void {
70 | this.beforeUnMountedList.push(fn)
71 | }
72 |
73 | unMounted(fn: () => void): void {
74 | this.unMountedList.push(fn)
75 | }
76 |
77 | emit(lifeName: keyof ComponentLifeCycleInstance) {
78 | this[`${lifeName}List`].forEach(fn => fn())
79 | }
80 |
81 | }
82 |
83 | // 生成类组件生命周期实例
84 | export function createClassComponentLife(component: ComponentInstance) {
85 | let lifeNames: [
86 | 'created',
87 | 'beforeMounted',
88 | 'readyMounted',
89 | 'mounted',
90 | 'beforeUnMounted',
91 | 'unMounted'
92 | ] = ['created', 'beforeMounted', 'readyMounted', 'mounted', 'beforeUnMounted', 'unMounted']
93 |
94 | let lifeCycle = new ComponentLifeCycle()
95 | component.life = lifeCycle
96 |
97 | lifeNames.forEach(lifeName => {
98 | let fn = component[lifeName]
99 | if(!fn) return
100 | lifeCycle[lifeName](fn.bind(component))
101 | })
102 |
103 | return lifeCycle
104 | }
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/mount/element.ts:
--------------------------------------------------------------------------------
1 | import { App } from "../app"
2 | import { watchProp } from "../reactive"
3 | import { isActiver, isOnEvent, isFunction, isObject } from "../utils"
4 | import {VElement, VNodeElement} from "../vnode"
5 | import { mountChildren } from "./mount"
6 | import { patchElementProp } from "./patch"
7 |
8 |
9 |
10 | export function mountElement(vnode: VElement, container: HTMLElement, anchor?: VNodeElement, app?: App) {
11 | const el = document.createElement(vnode.type)
12 | vnode.el = el
13 | mountElementProps(vnode)
14 | mountChildren(vnode.children, el, undefined, app)
15 | container.insertBefore(el, anchor!)
16 | }
17 |
18 | export function unmountElement(vnode: VElement, container: HTMLElement) {
19 | vnode.el.remove()
20 | }
21 |
22 | export function mountElementProps(vnode: VElement) {
23 | let el = vnode.el
24 | let props = vnode.props
25 |
26 | // 处理标签属性
27 | for (let prop in props) {
28 | let value = props[prop]
29 | if (isActiver(value)) {
30 | let firstValue = watchProp(value, (oldValue, newValue) => patchElementProp(oldValue, newValue, el, prop))
31 | setElementProp(el, prop, firstValue)
32 | } else {
33 | setElementProp(el, prop, value)
34 | }
35 | }
36 | }
37 |
38 | /**
39 | * 处理单个dom属性
40 | */
41 | export function setElementProp(el: HTMLElement, prop: string, value: any) {
42 | if (isOnEvent(prop) && isFunction(value)) {
43 | let pattern = /^on(.+)$/
44 | let result = prop.match(pattern)
45 | result && el.addEventListener(result[1].toLocaleLowerCase(), value.bind(el))
46 | return
47 | }
48 |
49 | switch (prop) {
50 | case 'className':
51 | el.className = String(value)
52 | break
53 | case 'style':
54 | if (isObject(value)) {
55 | value = mergeStyle(value)
56 | }
57 | default:
58 | el.setAttribute(prop, value)
59 | }
60 | }
61 |
62 | /**
63 | * 将对象形式的style转化为字符串
64 | */
65 | export function mergeStyle(style: Record): string {
66 | let styleStringList = []
67 | for (let cssAttr in style) {
68 | styleStringList.push(`${cssAttr}:${style[cssAttr]};`)
69 | }
70 | return styleStringList.join('')
71 | }
72 |
--------------------------------------------------------------------------------
/src/mount/mount.ts:
--------------------------------------------------------------------------------
1 | import { patch } from "./patch";
2 | import { watchVNode } from "../reactive";
3 | import { App } from "../app";
4 | import {
5 | VAlive,
6 | VArrayNode,
7 | VComponent,
8 | VElement,
9 | VFragment, VNodeElement,
10 | VText
11 | } from "../vnode";
12 | import { VNode, VNODE_TYPE } from "../vnode";
13 | import { mountElement, unmountElement } from "./element";
14 |
15 | export function mount(vnode: VNode, container: HTMLElement, anchor?: VNodeElement, app?: App) {
16 | switch (vnode.flag) {
17 | case VNODE_TYPE.ELEMENT:
18 | mountElement(vnode as VElement, container, anchor, app)
19 | break
20 | case VNODE_TYPE.TEXT:
21 | mountText(vnode as VText, container, anchor)
22 | break
23 | case VNODE_TYPE.FRAGMENT:
24 | mountFragment(vnode as VFragment, container, anchor, app)
25 | break
26 | case VNODE_TYPE.ARRAYNODE:
27 | mountArrayNode(vnode as VArrayNode, container, anchor, app)
28 | break
29 | case VNODE_TYPE.COMPONENT:
30 | mountComponent(vnode as VComponent, container, anchor, app)
31 | break
32 | case VNODE_TYPE.ALIVE:
33 | mountAlive(vnode as VAlive, container, anchor, app)
34 | break
35 | }
36 | }
37 |
38 | export function unmount(vnode: VNode, container: HTMLElement) {
39 | switch (vnode.flag) {
40 | case VNODE_TYPE.ELEMENT:
41 | unmountElement(vnode as VElement, container)
42 | break
43 | case VNODE_TYPE.TEXT:
44 | unmountText(vnode as VText, container)
45 | break
46 | case VNODE_TYPE.FRAGMENT:
47 | unmountFragment(vnode as VFragment, container)
48 | break
49 | case VNODE_TYPE.ARRAYNODE:
50 | unmountArrayNode(vnode as VArrayNode, container)
51 | break
52 | case VNODE_TYPE.COMPONENT:
53 | unmountComponent(vnode as VComponent, container)
54 | break
55 | }
56 | }
57 |
58 | export function mountChildren(children: Array, container: HTMLElement, anchor?: VNodeElement, app?: App) {
59 | children.forEach(child => mount(child, container, anchor, app))
60 | }
61 |
62 | export function mountText(vnode: VText, container: HTMLElement, anchor?: VNodeElement) {
63 | const el = document.createTextNode(vnode.children)
64 | vnode.el = el
65 | container.insertBefore(el, anchor!)
66 | }
67 |
68 | export function unmountText(vnode: VText, container: HTMLElement) {
69 | vnode.el.remove()
70 | }
71 |
72 | export function mountFragment(vnode: VFragment, container: HTMLElement, anchor?: VNodeElement, app?: App) {
73 | const start = document.createTextNode('')
74 | const end = document.createTextNode('')
75 | vnode.anchor = start
76 | vnode.el = end
77 | container.insertBefore(start, anchor!)
78 | mountChildren(vnode.children, container, anchor, app)
79 | container.insertBefore(end, anchor!)
80 | }
81 |
82 | export function unmountFragment(vnode: VFragment, container: HTMLElement) {
83 | const start = vnode.anchor
84 | const end = vnode.el
85 | let cur: ChildNode | null = start
86 | while (cur && cur !== end) {
87 | let next: ChildNode | null = cur.nextSibling
88 | cur.remove()
89 | cur = next
90 | }
91 | end.remove()
92 | }
93 |
94 | export function mountArrayNode(vnode: VArrayNode, container: HTMLElement, anchor?: VNodeElement, app?: App) {
95 | const start = document.createTextNode('')
96 | const end = document.createTextNode('')
97 | vnode.anchor = start
98 | vnode.el = end
99 | container.insertBefore(start, anchor!)
100 | mountChildren(vnode.children, container, anchor, app)
101 | container.insertBefore(end, anchor!)
102 | }
103 |
104 | export function unmountArrayNode(vnode: VArrayNode, container: HTMLElement, anchor?: HTMLElement) {
105 | const start = vnode.anchor
106 | const end = vnode.el
107 | let cur: ChildNode | null = start
108 | while (cur && cur !== end) {
109 | let next: ChildNode | null = cur.nextSibling
110 | cur.remove()
111 | cur = next
112 | }
113 | end.remove()
114 | }
115 |
116 | export function mountComponent(vNode: VComponent, container: HTMLElement, anchor?: VNodeElement, app?: App) {
117 | const root = vNode.root
118 | vNode.lifeStyleInstance.emit('readyMounted')
119 | mount(root, container, anchor, app)
120 | vNode.lifeStyleInstance.emit('mounted')
121 | }
122 |
123 | export function unmountComponent(vNode: VComponent, container: HTMLElement) {
124 | vNode.lifeStyleInstance.emit('beforeUnMounted')
125 | unmount(vNode.root, container)
126 | vNode.lifeStyleInstance.emit('unMounted')
127 | }
128 |
129 | export function mountAlive(vnode: VAlive, container: HTMLElement, anchor?: VNodeElement, app?: App) {
130 | let firstVNode = watchVNode(vnode, (oldVNode, newVNode) => patch(oldVNode, newVNode, container, app))
131 | vnode.vnode = firstVNode
132 | mount(firstVNode, container, anchor, app)
133 | }
134 |
--------------------------------------------------------------------------------
/src/mount/patch.ts:
--------------------------------------------------------------------------------
1 | import {mount, unmount} from "./mount";
2 | import { App } from "../app";
3 | import {VArrayNode} from "../vnode";
4 | import {VText} from "../vnode";
5 | import {VNode, VNODE_TYPE} from "../vnode";
6 | import {setElementProp} from "./element";
7 | import {VComponent} from "../vnode";
8 |
9 | export function isSameVNode(oldVNode: VNode, newVNode: VNode): boolean {
10 | return oldVNode.flag === newVNode.flag
11 | }
12 |
13 | // todo
14 | export function getNextSibling(vNode: VNode): ChildNode | null {
15 | switch (vNode.flag) {
16 | case VNODE_TYPE.TEXT:
17 | case VNODE_TYPE.ELEMENT:
18 | case VNODE_TYPE.ARRAYNODE:
19 | case VNODE_TYPE.FRAGMENT:
20 | return vNode.el!.nextSibling
21 | case VNODE_TYPE.COMPONENT:
22 | return (vNode as VComponent).root.el!.nextSibling
23 | case VNODE_TYPE.ALIVE:
24 | return getNextSibling(vNode.vnode!)
25 | }
26 | }
27 |
28 | export function patch(oldVNode: VNode, newVNode: VNode, container: HTMLElement, app?: App): void {
29 |
30 | // 如果两个节点引用一样不需要判断
31 | if (oldVNode === newVNode) return
32 |
33 | // 这里在判断相同类型的节点后可以做进一步优化
34 | if (isSameVNode(oldVNode, newVNode)) {
35 | let flag = oldVNode.flag = newVNode.flag
36 |
37 | if (flag === VNODE_TYPE.TEXT) {
38 | patchText(oldVNode, newVNode, container)
39 | } else if (flag === VNODE_TYPE.ARRAYNODE) {
40 | if (app?.options.arrayDiff) {
41 | patchArrayNodeT(oldVNode, newVNode, container)
42 | } else {
43 | patchArrayNode(oldVNode, newVNode, container)
44 | }
45 | } else {
46 | // const nextSibling = oldVNode?.el?.nextSibling
47 | const nextSibling = getNextSibling(oldVNode)
48 | unmount(oldVNode, container)
49 | mount(newVNode, container, nextSibling as ChildNode | undefined)
50 | }
51 |
52 | } else {
53 | // const nextSibling = oldVNode?.el?.nextSibling
54 | const nextSibling = getNextSibling(oldVNode)
55 | unmount(oldVNode, container)
56 | mount(newVNode, container, nextSibling as ChildNode | undefined)
57 | }
58 |
59 | }
60 |
61 | export function patchText(oldVNode: VText, newVNode: VText, container: HTMLElement) {
62 | oldVNode.el!.nodeValue = newVNode.children
63 | newVNode.el = oldVNode.el
64 | }
65 |
66 | export function patchElementProp(oldValue: any, newValue: any, el: HTMLElement, prop: string) {
67 | setElementProp(el, prop, newValue)
68 | }
69 |
70 | export function patchArrayNodeT(oldVNode: VArrayNode, newVNode: VArrayNode, container: HTMLElement) {
71 | if (!oldVNode.depArray) {
72 | patchArrayNode(oldVNode, newVNode, container)
73 | return
74 | }
75 |
76 | const oldDepArray = oldVNode.depArray
77 | const newDepArray = newVNode.depArray
78 | const oldChildren = oldVNode.children
79 | const newChildren = newVNode.children
80 |
81 | if (!oldDepArray.length || !newDepArray.length) {
82 | patchArrayNode(oldVNode, newVNode, container)
83 | return
84 | }
85 |
86 | newVNode.anchor = oldVNode.anchor
87 | newVNode.el = oldVNode.el
88 |
89 | type NodeInfo = { node: VNode, index: number, used: boolean }
90 | // 为映射做初始化
91 | let map = new Map>()
92 | oldDepArray.forEach((item, index) => {
93 | let arr = map.get(item)
94 | if (!arr) map.set(item, arr = [])
95 | arr.push({ node: oldChildren[index], index, used: false })
96 | })
97 |
98 | let getOld = (item: any) => {
99 | let arr = map.get(item)
100 | if (!arr) return false
101 |
102 | let index = arr.findIndex(alone => !alone.used)
103 |
104 | if (index > -1) return arr[index]
105 | else return false
106 | }
107 |
108 | let moveOld = (item: any, node: NodeInfo) => {
109 | let arr = map.get(item)
110 | if (!arr) return
111 |
112 | let index = arr.findIndex(alone => alone === node)
113 | arr.splice(index, 1)
114 | }
115 |
116 | let maxIndexSoFar = { node: oldChildren[0], index: 0 }
117 |
118 | newDepArray.forEach((item, newIndex) => {
119 | let old = getOld(item)
120 | if (old) {
121 | if (old.index < maxIndexSoFar.index) {
122 | let next: ChildNode | null
123 | if (newIndex > 0) {
124 | next = getNextSibling(newChildren[newIndex - 1]) as ChildNode | null
125 | } else {
126 | next = getNextSibling(maxIndexSoFar.node) as ChildNode | null
127 | }
128 |
129 | VNodeInsertBefore(container, old.node, next as ChildNode | undefined)
130 | // container.insertBefore(old.node.el!, next)
131 | } else {
132 | maxIndexSoFar = old
133 | }
134 |
135 | newChildren[newIndex] = old.node
136 | moveOld(item, old)
137 | } else {
138 | // let next = maxIndexSoFar.node.el!.nextSibling
139 | let next: ChildNode | null
140 | if (newIndex > 0) {
141 | next = getNextSibling(newChildren[newIndex - 1]) as ChildNode | null
142 | } else {
143 | next = getNextSibling(maxIndexSoFar.node) as ChildNode | null
144 | }
145 |
146 | let newNode = newChildren[newIndex]
147 | mount(newNode, container, next as HTMLElement | undefined)
148 | // maxIndexSoFar = { node: newNode, index: maxIndexSoFar.index + 1 }
149 | }
150 | })
151 |
152 |
153 | map.forEach(value => {
154 | value.forEach(item => {
155 | if (!item.used) {
156 | unmount(item.node, container)
157 | }
158 | })
159 | })
160 | }
161 |
162 | function patchArrayNode(oldVNode: VArrayNode, newVNode: VArrayNode, container: HTMLElement) {
163 | const nextSibling = oldVNode.el.nextSibling
164 | unmount(oldVNode, container)
165 | mount(newVNode, container, nextSibling as ChildNode | undefined)
166 | }
167 |
168 |
169 | /**
170 | * 将一个虚拟节点挂载到一个锚点前面
171 | */
172 | function VNodeInsertBefore(container: HTMLElement, node: VNode, next?: ChildNode) {
173 | if (node.flag === VNODE_TYPE.ELEMENT || node.flag === VNODE_TYPE.TEXT) {
174 | container.insertBefore(node.el!, next!)
175 | } else if (node.flag === VNODE_TYPE.COMPONENT) {
176 | container.insertBefore((node as VComponent).root.el!, next!)
177 | }
178 | else if (node.flag === VNODE_TYPE.ARRAYNODE || node.flag === VNODE_TYPE.FRAGMENT) {
179 | let start: ChildNode | null | undefined = node.anchor
180 | let nextToMove = start?.nextSibling
181 | let end: ChildNode | null | undefined = node.el
182 | while (start !== end) {
183 | container.insertBefore(start!, next!)
184 | start = nextToMove
185 | nextToMove = start?.nextSibling
186 | }
187 | container.insertBefore(end!, next!)
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/src/plugin.ts:
--------------------------------------------------------------------------------
1 | import {
2 | state,
3 | defineState,
4 | watch
5 | } from "./reactive";
6 | import { renderApi as h } from "./render";
7 |
8 | // todo
9 |
10 | // 实现插件功能
11 |
12 | export interface AppUtils {
13 | state: typeof state
14 | defineState: typeof defineState
15 | h: typeof h
16 | watch: typeof watch
17 | }
18 |
19 | export interface AppContext {
20 | utils: AppUtils
21 | }
22 |
23 | export interface AppPlugin {
24 | install(utils: AppContext): void
25 | }
26 |
27 | // 给插件提供的能力
28 | export const appUtils: AppUtils = {
29 | state,
30 | defineState,
31 | h,
32 | watch
33 | }
34 |
--------------------------------------------------------------------------------
/src/reactive/__test__.ts:
--------------------------------------------------------------------------------
1 | import { watch } from "./watch";
2 | import { reactive } from "./reactive";
3 | import { active } from "./active";
4 |
5 |
6 | let obj = reactive({ list: [1, 2, 3] })
7 | watch(active(() => obj.list), (o, n) => {
8 | console.log('改变了');
9 | // console.log(o, n);
10 | })
11 |
12 | // let a = [3]
13 | // obj.list.length = 2
14 | // obj.list[1] = a[0]
15 |
16 |
17 | let arr = new Proxy([1, 2, 3, 4, 5], {
18 | set(target, prop, value, receiver) {
19 | console.log(prop);
20 |
21 |
22 | // let old = target.slice()
23 | let res = Reflect.set(target, prop, value)
24 | // console.log(target);
25 | // let newV = target
26 | // console.log(old, newV);
27 |
28 | return res
29 | }
30 | })
31 |
32 | arr.splice(1, 2, 888)
--------------------------------------------------------------------------------
/src/reactive/active.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * 响应式对象
4 | */
5 | export class Activer {
6 | callback: () => T
7 | flag: string = 'activer'
8 |
9 | constructor(fn: () => T) {
10 | this.callback = fn
11 | }
12 |
13 | get value(): T {
14 | return this.callback()
15 | }
16 | }
17 |
18 | /**
19 | * 外置函数
20 | */
21 | export function active(fn: () => T): Activer {
22 | return new Activer(fn)
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/src/reactive/index.ts:
--------------------------------------------------------------------------------
1 | export * from './utils'
2 | export * from './reactive'
3 | export * from './ref'
4 | export * from './watch'
5 | export * from './active'
6 |
--------------------------------------------------------------------------------
/src/reactive/reactive.ts:
--------------------------------------------------------------------------------
1 | import { isArray, isObjectExact, isVNode } from "../utils"
2 | import { Watcher } from "./watch"
3 | import { runUpdate } from './update'
4 |
5 | // 目标对象到映射对象
6 | const targetMap = new WeakMap()
7 | // 全局变量watcher
8 | let activeWatcher: Watcher | null = null
9 | const REACTIVE = Symbol('reactive')
10 |
11 | /**
12 | * 实现响应式对象
13 | */
14 | export function reactive>(target: T): T {
15 | if (target[REACTIVE]) return target
16 |
17 | let handler: ProxyHandler = {
18 | get(target, prop, receiver) {
19 | if (prop === REACTIVE) return true
20 | const res = Reflect.get(target, prop, receiver)
21 |
22 | if (isObjectExact(res) && !isVNode(res)) {
23 | return reactive(res)
24 | }
25 |
26 | if (Array.isArray(res)) {
27 | track(target, prop)
28 | return reactiveArray(res, target, prop)
29 | }
30 |
31 | track(target, prop)
32 | return res
33 | },
34 |
35 | set(target, prop, value, receiver) {
36 | const res = Reflect.set(target, prop, value, receiver)
37 | trigger(target, prop)
38 | return res
39 | }
40 | }
41 |
42 | return new Proxy(target, handler)
43 | }
44 |
45 | /**
46 | * 设置响应式数组
47 | */
48 | function reactiveArray(targetArr: Array, targetObj: Record, Arrprop: string | symbol) {
49 | let handler: ProxyHandler> = {
50 | get(target, prop, receiver) {
51 | const res = Reflect.get(target, prop, receiver)
52 |
53 | if (isObjectExact(res)) {
54 | return reactive(res)
55 | }
56 |
57 | return res
58 | },
59 | set(target, prop, value, receiver) {
60 | const res = Reflect.set(target, prop, value, receiver)
61 | trigger(targetObj, Arrprop)
62 | return res
63 | }
64 | }
65 |
66 | return new Proxy(targetArr, handler)
67 | }
68 |
69 | /**
70 | * 响应触发依赖
71 | */
72 | export function trigger(target: Record, prop: string | symbol) {
73 | let mapping: Record> = targetMap.get(target)
74 | if (!mapping) return
75 |
76 | let mappingProp: Array = mapping[prop]
77 | if (!mappingProp) return
78 |
79 | // mappingProp.forEach(watcher => watcher.update(oldValue, newValue))
80 | mappingProp.forEach(watcher => {
81 | // 针对于对数组响应做特殊处理
82 | if (isArray(target[prop])) {
83 | watcher.nextDepArr = target[prop]
84 | }
85 | runUpdate(watcher)
86 | })
87 | }
88 |
89 | /**
90 | * 追踪绑定依赖
91 | */
92 | export function track(target: Record, prop: string | symbol) {
93 | if (!activeWatcher) return
94 |
95 | let mapping: Record> = targetMap.get(target)
96 | if (!mapping) targetMap.set(target, mapping = {})
97 |
98 | let mappingProp: Array = mapping[prop]
99 | if (!mappingProp) mappingProp = mapping[prop] = []
100 |
101 | // 针对于对数组响应做特殊处理
102 | if (isArray(target[prop])) {
103 | if (activeWatcher.depArr) {
104 | activeWatcher.depArr = false
105 | } else {
106 | if (activeWatcher.depArr === undefined) {
107 | activeWatcher.depArr = target[prop].slice()
108 | }
109 | }
110 | }
111 |
112 | mappingProp.push(activeWatcher)
113 | }
114 |
115 | // 设置全局变量
116 | export function setActiver(fn: Watcher | null) {
117 | activeWatcher = fn
118 | }
119 | // 设置全局变量
120 | export function getActiver(): Watcher | null {
121 | return activeWatcher
122 | }
123 |
124 |
--------------------------------------------------------------------------------
/src/reactive/ref.ts:
--------------------------------------------------------------------------------
1 | import { track, trigger } from './reactive'
2 |
3 | export class RefImpl {
4 | private _value: T
5 | private readonly _target: Record
6 |
7 | constructor(value: T) {
8 | this._value = value
9 | this._target = { value: this._value }
10 | }
11 |
12 | get value() {
13 | track(this._target, 'value')
14 | return this._value
15 | }
16 |
17 | set value(value) {
18 | this._value = value
19 | this._target.value = this._value
20 | trigger(this._target, 'value')
21 | }
22 | }
23 |
24 |
25 | export function ref(value: T): RefImpl {
26 | return new RefImpl(value)
27 | }
--------------------------------------------------------------------------------
/src/reactive/update.ts:
--------------------------------------------------------------------------------
1 | import { Watcher } from "./watch";
2 |
3 | let updating = false
4 | const watcherTask: Array = []
5 |
6 | export function runUpdate(watcher: Watcher) {
7 | let index: number = watcherTask.indexOf(watcher)
8 | if (!(index > -1)) watcherTask.push(watcher)
9 | if (!updating) {
10 | updating = true
11 | Promise.resolve()
12 | .then(() => {
13 | let watcher: Watcher | undefined = undefined
14 | while (watcher = watcherTask.shift()) {
15 | watcher.update()
16 | }
17 | }).finally(() => {
18 | updating = false
19 | })
20 | }
21 | }
--------------------------------------------------------------------------------
/src/reactive/utils.ts:
--------------------------------------------------------------------------------
1 | import { ref } from './ref'
2 | import { reactive } from './reactive'
3 |
4 |
5 | export function state(value: T) {
6 | return ref(value)
7 | }
8 |
9 | export function defineState>(target: T): T {
10 | return reactive(target)
11 | }
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/reactive/watch.ts:
--------------------------------------------------------------------------------
1 | import { setActiver } from "./reactive"
2 | import { active, Activer } from './active'
3 | import { VNode, VNODE_TYPE, OriginVNode } from "../vnode"
4 | import { isActiver } from "../utils"
5 | import { createVNode } from "../render"
6 | import { VArrayNode } from "../vnode"
7 | import { VAlive } from "../vnode"
8 |
9 | type Meta = {
10 | targetPropOldValue: any,
11 | targetPropnewValue: any
12 | }
13 |
14 | /**
15 | * 观察者
16 | * 观察数据的变化
17 | */
18 | export class Watcher {
19 | value: T
20 | callback: (oldValue: T, newValue: T, meta?: Meta) => void
21 | activeProps: Activer
22 | depArr?: Array | false
23 | nextDepArr?: Array
24 |
25 | constructor(activeProps: Activer, callback: (oldValue: T, newValue: T, meta?: Meta) => void) {
26 | setActiver(this)
27 | this.value = activeProps.value
28 | this.callback = callback
29 | this.activeProps = activeProps
30 | setActiver(null)
31 | }
32 |
33 | update() {
34 | let newValue = this.activeProps.value
35 | let oldValue = this.value
36 | this.value = newValue
37 | let meta = { targetPropOldValue: this.depArr, targetPropnewValue: this.nextDepArr }
38 | this.callback(oldValue, newValue, meta)
39 | this.depArr = this.nextDepArr?.slice()
40 | }
41 | }
42 |
43 |
44 | /**
45 | * 监控自定义响应式属性
46 | */
47 | export function watch(activeProps: (() => T) | Activer, callback: (oldValue: T, newValue: T) => void): Watcher {
48 | if (!isActiver(activeProps)) activeProps = active(activeProps)
49 | return new Watcher(activeProps, function (oldValue: T, newValue: T) {
50 | callback(oldValue, newValue)
51 | })
52 | }
53 |
54 | /**
55 | * 监控可变状态dom
56 | */
57 | export function watchVNode(activeVNode: VAlive, callback: (oldVNode: VNode, newVNode: VNode) => void): VNode {
58 | let watcher = new Watcher(activeVNode.activer, function (oldValue: OriginVNode, newValue: OriginVNode, meta) {
59 | const oldVNode = oldValue as VNode
60 | const newVNode = createVNode(newValue)
61 |
62 | // 对于数组节点后期需要记录它的响应式数组用于节点更新
63 | if (oldVNode.flag === VNODE_TYPE.ARRAYNODE) {
64 | (oldVNode).depArray = meta?.targetPropOldValue
65 | }
66 | // 对于数组节点后期需要记录它的响应式数组用于节点更新
67 | if (newVNode.flag === VNODE_TYPE.ARRAYNODE) {
68 | (newVNode).depArray = meta?.targetPropnewValue
69 | }
70 |
71 | callback(oldVNode, newVNode)
72 | watcher.value = newVNode
73 | activeVNode.vnode = newVNode
74 | })
75 |
76 | return watcher.value = createVNode(watcher.value)
77 | }
78 |
79 |
80 | /**
81 | * 监控可变dom的prop
82 | */
83 | export function watchProp(activeProp: Activer, callback: (oldVNode: VNode, newVNode: VNode) => void): any {
84 | return new Watcher(activeProp, callback).value
85 | }
86 |
--------------------------------------------------------------------------------
/src/render.ts:
--------------------------------------------------------------------------------
1 | import {active} from "./reactive";
2 | import {Activer} from './reactive'
3 | import {
4 | isActiver,
5 | isArray,
6 | isArrayNode,
7 | isFragment,
8 | isFunction,
9 | isOnEvent,
10 | isString,
11 | isText,
12 | isVNode} from "./utils";
13 | import {
14 | ClassComponentType,
15 | ComponentType,
16 | FunctionComponentType,
17 | VComponent
18 | } from "./vnode";
19 | import {
20 | OriginVNode,
21 | VNode,
22 | VNODE_TYPE,
23 | VNodeProps
24 | } from "./vnode";
25 | import {
26 | ArraySymbol,
27 | AliveSymbol,
28 | TextSymbol,
29 | FragmentSymbol,
30 | VComponentSymbol
31 | } from "./vnode";
32 | import {ComponentLifeCycle, createClassComponentLife} from "./lifeCycle";
33 | import {appUtils} from "./plugin";
34 |
35 | /**
36 | * 传说中的render函数
37 | */
38 |
39 | export type HSymbol = typeof TextSymbol | typeof FragmentSymbol | typeof AliveSymbol | typeof ArraySymbol
40 | export type HType = string | HSymbol | ComponentType
41 | export type H = (type: HType, props?: VNodeProps, children?: OriginVNode | Array) => VNode
42 |
43 | /**
44 | * 函数转化为activer转化为VAlive
45 | * activer转化为VAlive
46 | * string转化为VText
47 | * 数组转化为VArray
48 | * 其他转化为VText
49 | */
50 | export function createVNode(originVNode: OriginVNode): VNode {
51 | if (isVNode(originVNode)) return originVNode
52 |
53 | if (isFunction(originVNode)) return renderAlive(active(originVNode))
54 | else if (isActiver(originVNode)) return renderAlive(originVNode)
55 | else if (isString(originVNode)) return renderText(originVNode)
56 | else if (isArray(originVNode)) return renderArrayNode(originVNode.map(item => createVNode(item)))
57 | else {
58 | // todo
59 | let value = originVNode
60 | let retText: VNode
61 | if (!value && typeof value !== 'number') retText = renderText('')
62 | else retText = renderText(String(value))
63 | return retText
64 | }
65 | }
66 |
67 |
68 | /**
69 | * text(不需要props)、fragment(不需要props)、element、component为显性创建
70 | * array(不需要props)、alive(不需要props)为隐形创建
71 | */
72 | export function renderApi(type: HType, props?: VNodeProps | null, children?: OriginVNode | Array): VNode {
73 | return render(type, props || undefined, children)
74 | }
75 |
76 | export function render(type: HType, originProps?: VNodeProps, originChildren?: OriginVNode | Array): VNode {
77 | // text的children比较特殊先处理
78 | if (isText(type)) {
79 | return renderText(String(originChildren))
80 | }
81 |
82 | // 预处理 处理为单个的children
83 | let originChildrenList: Array = []
84 | if (isArray(originChildren)) originChildrenList.push(...originChildren)
85 | else originChildrenList.push(originChildren)
86 | // 创建VNode列表
87 | let vNodeChildren: Array = originChildrenList.map(originChild => createVNode(originChild))
88 |
89 | // 属性预处理
90 | let props: VNodeProps = originProps || {}
91 | handleProps(props)
92 |
93 | if (isString(type)) return renderElement(type, props , vNodeChildren)
94 | if (isFragment(type)) return renderFragment(vNodeChildren)
95 | if (isArrayNode(type)) return renderArrayNode(vNodeChildren)
96 | if (isFunction(type)) return renderComponent(type, props, vNodeChildren)
97 | throw '传入参数不合法'
98 | }
99 |
100 |
101 | /**
102 | * 对属性进行预处理
103 | */
104 | function handleProps(originProps: VNodeProps): VNodeProps {
105 | for (const prop in originProps) {
106 | // 以on开头的事件不需要处理
107 | if (
108 | !isOnEvent(prop) &&
109 | isFunction(originProps[prop])
110 | ) {
111 | // 如不为on且为函数则判断为响应式
112 | originProps[prop] = active(originProps[prop])
113 | }
114 | }
115 |
116 | return originProps
117 | }
118 |
119 | function renderText(text: string): VNode {
120 | return {
121 | type: TextSymbol,
122 | flag: VNODE_TYPE.TEXT,
123 | children: text
124 | }
125 | }
126 |
127 | function renderElement(tag: string, props: Record, children: Array): VNode {
128 | return {
129 | type: tag,
130 | flag: VNODE_TYPE.ELEMENT,
131 | props,
132 | children
133 | }
134 | }
135 |
136 | function renderFragment(children: Array): VNode {
137 | return {
138 | type: FragmentSymbol,
139 | flag: VNODE_TYPE.FRAGMENT,
140 | children
141 | }
142 | }
143 |
144 | function renderArrayNode(children: Array): VNode {
145 | return {
146 | type: ArraySymbol,
147 | flag: VNODE_TYPE.ARRAYNODE,
148 | children
149 | }
150 | }
151 |
152 | function createComponentProps(props: VNodeProps): VNodeProps {
153 | let componentProps:VNodeProps = {}
154 | for (const prop in props) {
155 | let curProp = props[prop]
156 | if (isActiver(curProp)) {
157 | Object.defineProperty(componentProps, prop, {
158 | get() {
159 | return (curProp as Activer).value
160 | }
161 | })
162 | } else {
163 | componentProps[prop] = curProp
164 | }
165 | }
166 |
167 | return componentProps
168 | }
169 |
170 | // 渲染一个活跃的节点
171 | function renderAlive(activer: Activer): VNode {
172 | return {
173 | type: AliveSymbol,
174 | flag: VNODE_TYPE.ALIVE,
175 | activer
176 | }
177 | }
178 |
179 | // 判断是普通函数还是构造函数
180 | function renderComponent(component: ComponentType, props: VNodeProps, children: Array): VComponent {
181 | let componentProps = createComponentProps(props)
182 |
183 | if (
184 | component.prototype &&
185 | component.prototype.render &&
186 | isFunction(component.prototype.render)
187 | ) {
188 | let ClassComponent = component as ClassComponentType
189 | let result = new ClassComponent(componentProps, children)
190 |
191 | result.props = componentProps
192 | result.children = children
193 |
194 | let lifeCycle = createClassComponentLife(result)
195 |
196 | let vn = result.render(renderApi)
197 | lifeCycle.emit('created')
198 | let vc: VComponent = {
199 | type: VComponentSymbol,
200 | root: createVNode(vn),
201 | props: componentProps,
202 | children: children,
203 | flag: VNODE_TYPE.COMPONENT,
204 | lifeStyleInstance: lifeCycle
205 | }
206 |
207 | lifeCycle.emit('beforeMounted')
208 |
209 | return vc
210 | } else {
211 | let FunctionComponent = component as FunctionComponentType
212 | let lifeCycle = new ComponentLifeCycle()
213 | let vn = FunctionComponent({props: componentProps, children: children, life: lifeCycle, utils: appUtils})
214 | lifeCycle.emit('created')
215 | let vc: VComponent = {
216 | type: VComponentSymbol,
217 | root: createVNode(vn),
218 | props: componentProps,
219 | children: children,
220 | flag: VNODE_TYPE.COMPONENT,
221 | lifeStyleInstance: lifeCycle
222 | }
223 |
224 | lifeCycle.emit('beforeMounted')
225 | return vc
226 | }
227 | }
228 |
229 |
230 |
231 |
232 |
233 |
234 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { VNode, VNODE_TYPE } from './vnode'
2 | import { Activer } from './reactive'
3 | import { FragmentSymbol, TextSymbol, ArraySymbol } from './vnode'
4 |
5 | export function isString(content: unknown): content is string {
6 | return typeof content === 'string'
7 | }
8 |
9 | export function isFunction(content: unknown): content is Function {
10 | return typeof content === 'function'
11 | }
12 |
13 | export function isFragment(content: unknown): boolean {
14 | return content === FragmentSymbol
15 | }
16 |
17 | export function isArrayNode(content: unknown): boolean {
18 | return content === ArraySymbol
19 | }
20 |
21 | export function isText(content: unknown): boolean {
22 | return content === TextSymbol
23 | }
24 |
25 | export function isActiver(content: unknown): content is Activer {
26 | return isObject(content) && (content as Record).flag === 'activer'
27 | }
28 |
29 | export function isVNode(content: unknown): content is VNode {
30 | return isObject(content) && (content as Record).flag in VNODE_TYPE
31 | }
32 |
33 | export function isObjectExact(content: unknown): content is Object {
34 | return isObject(content) && content.constructor === Object
35 | }
36 |
37 | export function isOnEvent(str: string): boolean {
38 | return /^on.+$/.test(str)
39 | }
40 |
41 | export function isObject(content: unknown): content is object {
42 | return typeof content === 'object' && content !== null
43 | }
44 |
45 |
46 | export function isArray(content: unknown): content is Array {
47 | return Array.isArray(content)
48 | }
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/vnode/alive.ts:
--------------------------------------------------------------------------------
1 | import { Activer } from "../reactive"
2 | import { VNode, VNODE_TYPE, OriginVNode } from "./vnode"
3 |
4 | export const AliveSymbol = Symbol('Alive')
5 | export type VAliveType = typeof AliveSymbol
6 | export interface VAlive extends VNode {
7 |
8 | // 虚拟节点类型
9 | type: symbol,
10 | // 虚拟节点表标识
11 | flag: VNODE_TYPE.ELEMENT
12 |
13 | activer: Activer
14 | // 目前存在的节点
15 | vnode: VNode
16 | }
17 |
--------------------------------------------------------------------------------
/src/vnode/array.ts:
--------------------------------------------------------------------------------
1 | import {VNode, VNODE_TYPE, VNodeElement} from "./vnode";
2 |
3 | export const ArraySymbol = Symbol('ArrayNode');
4 | export type VArrayType = typeof ArraySymbol
5 | export interface VArrayNode extends VNode {
6 |
7 | // 虚拟节点类型
8 | type: symbol,
9 |
10 | // 虚拟节点子节点
11 | children: Array,
12 |
13 | // 虚拟节点表标识
14 | flag: VNODE_TYPE.ARRAYNODE
15 |
16 | // 锚点
17 | anchor: Text,
18 | el: Text,
19 |
20 | // 记录当前数组节点依赖的响应式数组
21 | depArray: Array
22 | }
23 |
--------------------------------------------------------------------------------
/src/vnode/component.ts:
--------------------------------------------------------------------------------
1 | import {VNode, VNODE_TYPE, OriginVNode, VNodeProps} from "./vnode";
2 | import { ComponentLifeCycle } from '../lifeCycle'
3 | import { ComponentInstance } from '../component'
4 | import {AppUtils} from '../plugin'
5 |
6 | export interface ComponentContext {
7 | props: Record
8 | children: Array
9 | life: ComponentLifeCycle
10 | utils: AppUtils
11 | }
12 |
13 | /** 函数组件类型 */
14 | export interface FunctionComponentType {
15 | (context: ComponentContext): OriginVNode
16 | }
17 |
18 | /** 类组件类型 */
19 | export interface ClassComponentType {
20 | new (props: Record, children: Array): ComponentInstance
21 | }
22 |
23 | export type ComponentType = FunctionComponentType | ClassComponentType
24 |
25 | export const VComponentSymbol = Symbol('VComponent')
26 | export type VComponentType = typeof VComponentSymbol
27 |
28 | export interface VComponent extends VNode {
29 |
30 | // 虚拟节点类型
31 | type: VComponentType,
32 |
33 | // 虚拟节点属性
34 | props: VNodeProps,
35 |
36 | // 虚拟节点子节点
37 | children: Array,
38 |
39 | // 虚拟节点表标识
40 | flag: VNODE_TYPE.COMPONENT
41 |
42 | root: VNode,
43 |
44 | lifeStyleInstance: ComponentLifeCycle
45 |
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/src/vnode/element.ts:
--------------------------------------------------------------------------------
1 | import {VNode, VNODE_TYPE, VNodeElement, VNodeProps} from "./vnode";
2 |
3 | export interface VElement extends VNode {
4 |
5 | // 虚拟节点类型
6 | type: string,
7 |
8 | // 虚拟节点属性
9 | props: VNodeProps,
10 |
11 | // 虚拟节点子节点
12 | children: Array,
13 |
14 | // 虚拟节点表标识
15 | flag: VNODE_TYPE.ELEMENT
16 |
17 | el: HTMLElement
18 | }
19 |
--------------------------------------------------------------------------------
/src/vnode/fragment.ts:
--------------------------------------------------------------------------------
1 | import {VNode, VNODE_TYPE, VNodeElement} from "./vnode";
2 |
3 | export const FragmentSymbol = Symbol('Fragment');
4 | export interface VFragment extends VNode {
5 |
6 | // 虚拟节点类型
7 | type: symbol,
8 |
9 | // 虚拟节点子节点
10 | children: Array,
11 |
12 | // 虚拟节点表标识
13 | flag: VNODE_TYPE.FRAGMENT
14 |
15 | // 锚点
16 | anchor: Text,
17 | el: Text
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/vnode/index.ts:
--------------------------------------------------------------------------------
1 | export * from './text'
2 | export * from './element'
3 | export * from './fragment'
4 | export * from './component'
5 | export * from './array'
6 | export * from './alive'
7 | export * from './vnode'
8 |
--------------------------------------------------------------------------------
/src/vnode/text.ts:
--------------------------------------------------------------------------------
1 | import {VNode, VNODE_TYPE, VNodeElement} from "./vnode";
2 |
3 |
4 | export const TextSymbol = Symbol('Text');
5 | export interface VText extends VNode {
6 |
7 | // 虚拟节点类型
8 | type: symbol,
9 |
10 | // 虚拟节点子节点
11 | children: string,
12 |
13 | // 虚拟节点表标识
14 | flag: VNODE_TYPE.TEXT
15 |
16 | el: Text
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/vnode/vnode.ts:
--------------------------------------------------------------------------------
1 | import { Activer } from "../reactive"
2 | import { ComponentType } from './component'
3 |
4 | /**
5 | * 虚拟dom节点类型枚举
6 | */
7 | export enum VNODE_TYPE {
8 | // 普通元素节点类型
9 | ELEMENT,
10 | // 文本节点类型
11 | TEXT,
12 | FRAGMENT,
13 | COMPONENT,
14 | ARRAYNODE,
15 | ALIVE
16 | }
17 |
18 | export type VNodeElement = ChildNode
19 |
20 | /**
21 | * 虚拟dom接口类型
22 | */
23 | export interface VNode {
24 |
25 | // 虚拟节点类型
26 | type: string | symbol | ComponentType,
27 |
28 | // 虚拟节点表标识
29 | flag: VNODE_TYPE
30 |
31 | // 虚拟节点属性
32 | props?: Record,
33 |
34 | // 虚拟节点子节点
35 | children?: Array | string,
36 |
37 | // 真实节点
38 | el?: VNodeElement
39 | anchor?: VNodeElement
40 |
41 | activer?: Activer
42 | // 目前存在的节点
43 | vnode?: VNode
44 |
45 | }
46 |
47 | // 未经过标准化的childVNode类型
48 | export type NotFunctionOriginVNode= string | VNode | Array | Activer
49 | export type WithFunctionOriginVNode= (() => OriginVNode) | NotFunctionOriginVNode
50 | export type OriginVNode= WithFunctionOriginVNode | Exclude
51 |
52 | /** 虚拟节点属性 todo */
53 | export interface VNodeProps {
54 | [propName: string]: any
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/test/index.test.ts:
--------------------------------------------------------------------------------
1 | import { render } from '../src'
2 | import { VNODE_TYPE } from '../src/vnode';
3 |
4 |
5 | test('function: render(type, props, children): VNode', () => {
6 | expect(render('span', null, [])).toEqual({
7 | type: 'span',
8 | flag: VNODE_TYPE.ELEMENT,
9 | props: {},
10 | children: []
11 | });
12 | })
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
4 | "module": "CommonJS", /* Specify what module code is generated. */
5 | "outDir": "./dist", /* Specify an output folder for all emitted files. */
6 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
7 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
8 | "strict": true, /* Enable all strict type-checking options. */
9 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
10 | }
11 | }
12 |
--------------------------------------------------------------------------------