开源软件名称:mvvm-reactive-view
开源软件地址:https://gitee.com/masx200/mvvm-reactive-view
开源软件介绍:
mvvm-reactive-view这是一个实验性项目,此代码库仅供学习交流使用 面向未来的,轻量级,响应式,mvvm ,构建视图,声明式,组件化,基于 webcomponent ,基于虚拟 dom 基于 Proxy ,支持 jsx 和 hyperscript ,前端 javascript 库,完全使用TypeScript 编写虽然使用了虚拟dom ,但是,与react ,vue 等之类的前端框架有本质上的不同,不使用diff 算法,响应式状态直接与元素绑定,高效更新,性能更强不使用 diff 算法,使用 proxy 精准监听状态变化,高效更新视图,状态都是响应式,可观察的对象,每次状态改变不会重新生成虚拟 dom ,可实现最小化DOM 操作使用响应式状态管理全局共享状态,抛弃 redux,vuex,mobx ,响应式状态可以独立于组件存在有着面向未来的函数式API ,提供与React Hooks 相同级别的逻辑组合功能,方便复用逻辑和重用,抛弃mixin (混入 )和hoc (高阶组件 ),Render Props 支持组件局部css 样式,仅在组件内有效,不会全局生效由于使用了 Proxy ,所以不支持 IE 浏览器,而且 Proxy 不可 polyfill 兼容的浏览器浏览器要求原生支持Proxy 和ECMASCRIPT2017 以上 EDGE,CHROME,FIREFOX,SAFARI
安装 npm 模块cnpm install --save https://github.com/masx200/mvvm-reactive-view.git 或者 yarn add https://github.com/masx200/mvvm-reactive-view.git 从 cdn 获取模块开发模式https://cdn.jsdelivr.net/gh/masx200/mvvm-reactive-view@latest/dist/index.js 生产模式https://cdn.jsdelivr.net/gh/masx200/mvvm-reactive-view@latest/dist/index.min.js 关于 Proxy https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy polyfillimport "@masx200/mvvm-reactive-view/dist/polyfill.js"; <script src="https://cdn.jsdelivr.net/gh/masx200/mvvm-reactive-view@latest/dist/polyfill.js"></script> 需要webcomponent custom-elements polyfill https://github.com/webcomponents/polyfills/tree/master/packages/custom-elements ECMAScript2019 polyfill由于使用了 ECMAScript2019 的 api ,所以需要自行添加 polyfill 需要 Object.fromEntries 和Array.prototype.flat 的 polyfill https://github.com/zloirock/core-js https://github.com/tc39/proposal-object-from-entries https://tc39.es/proposal-flatMap/ 使用 npm 模块import { Switchable, computed, createComponent, useCreated, useUpdated, useMounted, useUnMounted, Condition, Directives, watch, html, h, MountElement, createRef, createElement, createState, render} from "@masx200/mvvm-reactive-view"; 快速上手,可在浏览器中运行而不需要编译工具index.js
import { Switchable, computed, createComponent, useCreated, useUpdated, useMounted, useUnMounted, Condition, Directives, watch, html, h, MountElement, createRef, createElement, createState} from "https://cdn.jsdelivr.net/gh/masx200/mvvm-reactive-view@latest/dist/index.min.js";const inputref = createRef();const state1 = createState("hello");const stylestate = createState({ display: "block", width: "700px", color: "" });const vdom = html` <div style=${{ display: "block", width: "500px" }}>hello world!</div> <input style="width:800px" @input=${(e) => (state1.value = e.target.value)} *ref=${inputref} @change=${(e) => (state1.value = e.target.value)} id="code16" class="col-lg-12 col-md-12 col-sm-12 col-xs-12 snippet code16d form-control" value=${state1} /> <h1 style=${stylestate}>mvvm-reactive-view</h1> <button @click=${() => { stylestate.color = "red"; }} > red </button> <button @click=${() => { stylestate.color = "green"; }} > green </button>`;watch(state1, console.log);watch(stylestate, console.log);console.log(vdom, inputref);MountElement(vdom, document.getElementById("root")); index.html
<script src="https://cdn.jsdelivr.net/gh/masx200/mvvm-reactive-view@latest/dist/polyfill.js"></script><div id="root"></div><script type="module" src="./index.js"></script> 支持jsx 和使用 hyperscript 为什么选择jsx ?而不是template ?在体积方面,template 编译器远大于jsx 编译器。 浏览器中运行的JSX 编译器HTM (Hyperscript Tagged Markup) 体积小于 1KB jsx 的表现能力明显强于template ,template 中无法写函数与对象,只能写字符串,
类似vue 和angular 的模板DSL 会让人很难理解,模板dsl 不如jsx 灵活 JSX 非常流行,使用过react-jsx 的人可以轻松使用,学习成本比较低
但是与react-jsx 有一些不同,例如 属性名不使用驼峰命名等等 可在 rollup 或webpack 中, 使用 babel-plugin-htm 或者 @babel/plugin-transform-react-jsx 预编译成虚拟 dom https://github.com/developit/htm https://github.com/hyperhype/hyperscript https://github.com/developit/htm/tree/master/packages/babel-plugin-htm https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-jsx https://babeljs.io/docs/en/babel-plugin-transform-react-jsx 还需要使用babel-preset-env 包含core-js@3 和 "@babel/plugin-proposal-class-properties" { "plugins": [ [ "@babel/plugin-transform-react-jsx", { "pragma": "h", "pragmaFrag": "\"\"" } ], [ "babel-plugin-htm", { "tag": "html", "pragma": "h" } ], "@babel/plugin-proposal-optional-catch-binding", "@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-class-properties" ], "presets": [ [ "@babel/preset-env", { "corejs": 3, "useBuiltIns": "usage", "targets": [ "last 1 edge version", "last 1 safari version", "last 1 chrome version", "last 1 firefox version" ] } ] ]} 响应式状态对象 ReactiveState ,可以独立于组件存在,可以在任何地方使用,ReactiveState 状态改变触发Event ,触发函数也已经用 lodash 的debounce 函数包装成防抖函数,保证了短时间内只能触发一次事件
ReactiveState ,基于 Proxy ,
基于Proxy 的深层数据劫持监听,对于数组Array 和普通对象Plain Object 理论上无限层次的数据观察代理如果状态跟视图绑定,则状态改变引起界面刷新是异步的可修改其value 属性来改变状态的值,初始值类型一旦确定,后续只能把相同类型的值赋给它创建之后,对其value 赋值,必须和初始类型相同轻松使用全局共享状态,可以非常简单的集中统一管理,抛弃 redux,vuex,mobx /** * @param {number} init * @returns{{ get: () => number; increment: () => void; decrement: () => void;}} */function create(init) { /** * @type{{value:number}} */ const number = createState(init); function increment() { number.value++; } function decrement() { number.value--; } const get = () => number.value; const store = { get, increment, decrement }; return store;}const count = create(0);const mycomappclass = createComponent(() => { const vdom = ( <div> <h3> 点击数字</h3> <h2>number:{count.get()}</h2> <button onclick={count.increment}>increment</button> <button onclick={count.decrement}>decrement</button> </div> ); return vdom;});const vdom = [ createElement(mycomappclass), createElement(mycomappclass), createElement(mycomappclass)];const container = document.createElement("div");MountElement(vdom, container);document.body.appendChild(container); 条件渲染使用Condition 函数来实现条件渲染,返回值是虚拟dom var mystate = createState(true);var vdom = Condition( mystate, createElement("p", null, ["testtrue"]), createElement("div", undefined, "testfalese"));setTimeout(() => { mystate.value = false;}, 3000);const container = document.createElement("div");MountElement(vdom, container);document.body.appendChild(container); 组件化使用createComponent 来创建组件,传参是一个组件初始化函数,返回一个web component custom element 在组件初始化函数里面可以使用useMounted ,useUnMounted ,watch ,createState 等函数可以给组件设置默认属性,设置组件初始化函数的defaultProps 属性即可一个简单的helloworld 示例如下 const defaultProps = { cccccc: "bbbbbbb" };const css = `* { color: purple !important; font-size: 50px;}`;const Hellowordclass = createComponent( Object.assign( () => { return <div> hello world</div>; }, { css, defaultProps } ));const vdom = <Hellowordclass />;const container = document.createElement("div");MountElement(vdom, container);document.body.appendChild(container); 更新:可以不使用createComponent ,创建组件了,内部会自动使用createComponent ,自动转换组件初始化函数需要返回一个虚拟DOM 最后给组件初始化函数包裹一个createComponent 函数,返回一个web component custom element 组件局部 css 样式,设置组件初始化函数的css 属性即可,可以使用 rollup-plugin-postcss 或者to-string-loader 引入外部 css 文件转成字符串webpack to-string-loaderHow to import css string from css file? If you don't want to write CSS in JS, you can use to-string-loader of webpack, For example, the following configuration: [ { test: /[\\|\/]_[\S]*\.css$/, use: ["to-string-loader", "css-loader"] }]; If your CSS file starts with "_", CSS will use to-string-loader , such as: const css = require("./_index.css"); rollup postcss pluginimport postcss from "rollup-plugin-postcss";postcss({ minimize: true, extract: false, inject: false}); 样式隔离实现原理在运行时,使用浏览器自带的css 解析器,解析 css 文本变成cssrule ,然后给selectorText 添加前缀,再转换成 css 文本 转换前 div { transform: rotate(30deg);} 转换后 foobar div { transform: rotate(30deg);} 关于组件,元素的生命周期每个组件,每个元素都有 创建, 挂载,更新,卸载 的生命周期 使用MutationObserver 来高效的监听组件和元素的变化 给组件注册生命周期回调函数使用useCreated 来添加组件创建之后的回调函数 使用useMounted 来添加组件挂载之后的回调函数 使用useUpdated 来添加组件及其子节点更新之后的回调函数 使用useUnMounted 来添加组件卸载之后的回调函数 使用useMounted 和useUnMounted 来给组件添加挂载和卸载时执行的callback函数 ,只能在组件初始化函数里面使用,这些callback 函数都会异步执行组件卸载时,组件内创建的响应式状态会自动取消watch ,自动给元素删除事件监听器removeEventListener 组件挂载时,组件内创建的响应式状态会自动重新watch ,自动给元素添加事件监听器addEventListener 父子组件传数据组件之间的数据传递只能是从父组件到子组件的单向数据流,以json 格式传递参数 抛弃mixin (混入 )和hoc (高阶组件 ),让人难以理解,难以使用,它们已经被废弃。 https://blog.csdn.net/sinat_17775997/article/details/89181398 Mixin 带来的风险:
Mixin 可能会相互依赖,相互耦合,不利于代码维护 不同的 Mixin 中的方法可能会相互冲突 Mixin 非常多时,组件是可以感知到的, 甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性 HOC 和Render Props 的缺陷
HOC 需要在原组件上进行包裹或者嵌套,如果大量使用 HOC,将会产生非常多的嵌套,这让调试变得非常困难。 HOC 可以劫持 props ,在不遵守约定的情况下也可能造成冲突。 更好的逻辑组合复用方法受到 Vue Composition API 和React Hooks 的启发, 集各家所长,但是跟它们有很大不同, 响应式状态可以独立于组件存在,watch ,computed ,createState 函数可以在组件外使用 基于函数的 API 提供与React Hooks 相同级别的逻辑组合功能,但有一些重要的区别。 与React hooks 不同,该组件初始化函数仅被调用一次 使用useMounted 和useUnMounted 来给组件添加挂载和卸载时执行的函数,只能在组件初始化函数里面使用使用watch 函数来监听状态的变化,执行回调函数,可在任何地方使用此函数,传参 ReactiveState ,或者 ReactiveState 数组,回调函数参数是unwrapped state 的数组,返回一个取消观察 cancelwatch 函数函数watch 的回调函数已经自动使用lodash 的debounce 方法包装成防抖函数了,确保短时间内回调函数只执行一次var mystate = createState("aaeeqtt");const mycom = createComponent( Object.assign( (props, children) => { useCreated(() => { console.log("life-cycle-created"); }); useUpdated(() => { console.log("life-cycle-updated"); }); useMounted(() => { console.log("life-cycle-mounted1"); }); useUnMounted(() => { console.log("life-cycle-unmounted"); }); watch(props.cccccc, console.log); return createElement("div", null, [ "wwwwwwwwwwww", createElement("div", null, ["createComponent"]), children, createElement
|
请发表评论