├── .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 | [![OSCS Status](https://www.oscs1024.com/platform/badge/yanyunwu/vact.svg?size=small)](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 | ![基本示例](./static/基本示例.gif) 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 | ![双向绑定](./static/双向绑定.gif) 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 | ![示例](./static/props.gif) 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 | ![示例](./static/component.gif) 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 | --------------------------------------------------------------------------------