//src/qnd-react-dom.js import QndReact from './qnd-react'; import * as snabbdom from 'snabbdom'; import propsModule from 'snabbdom/modules/props'; ... //QndReactDom 告诉 QndReact 如何更新 DOM QndReact.__updater = () => {//当调用 this.setState 的时候更新 DOM 逻辑 } 无论何时我们调用 this.setState({...}),我们都需要比较组件的 oldVNode 和在组件上调用了 render 方法之后生成的 newVNode 。为了进行比较,我们在类组件上添加 __vNode 属性,以维护该组件当前的 VNode 实例 。
//src/qnd-react.js ... const createElement = (type, props = {}, ...children) => {/*** 如果是类组件* 1.创建一个实例* 2.调用实例的 render 方法*/if (type.prototype && type.prototype.isQndReactClassComponent) {const componentInstance = new type(props);componentInstancecomponentInstance.__vNode = componentInstance.render();return componentInstance.__vNode;}//如果是函数组件,那么调用它,并返回执行结果if (typeof (type) == 'function') {return type(props);}return h(type, { props }, children); }; ... 现在我们来在 Component 的基类中实现 setState 方法 。
//src/qnd-react.js ... class Component {constructor() { }componentDidMount() { }setState(partialState) {this.state = {...this.state,...partialState}//调用 QndReactDom 提供的 __updater 方法QndReact.__updater(this);}render() { } } ... 处理 QndReactDom 中的 __updater 方法 。
//src/qnd-react-dom.js ... QndReact.__updater = (componentInstance) => {//当调用 this.setState 的时候更新 DOM 逻辑//获取在 __vNode 上存储的 oldVNodeconst oldVNode = componentInstance.__vNode;//获取 newVNodeconst newVNode = componentInstance.render();//更新 __vNodecomponentInstance.__vNode = reconcile(oldVNode, newVNode); } ... export default QndReactDom; OK,我们在 Counter 组件中增加 state 来检验我们的 setState 实现是否生效 。
//src/counter.js import QndReact from './qnd-react'; export default class Counter extends QndReact.Component {constructor(props) {super(props);this.state = {count: 0}// update the count every secondsetInterval(() => {this.setState({count: this.state.count + 1})}, 1000);}componentDidMount() {console.log('Component mounted');}render() {return <p>Count: {this.state.count}</p>} } 太棒啦,现在 Counter 组件运行情况与我们预期完全一致 。
我们继续添加 componentDidMount 的生命周期钩子函数 。Snabbdom 提供了一些钩子函数,通过他们,我们可以知道真实DOM上面是否有添加,删除或是更新了虚拟DOM节点,你可以在此处了解更多信息 。
//src/qnd-react.js import { h } from 'snabbdom'; const createElement = (type, props = {}, ...children) => {/*** 如果是类组件* 1.创建一个实例* 2.调用实例的 render 方法*/if (type.prototype && type.prototype.isQndReactClassComponent) {const componentInstance = new type(props);componentInstancecomponentInstance.__vNode = componentInstance.render();return componentInstance.__vNode;//增加钩子函数(当虚拟DOM被添加到真实DOM节点上时)componentInstance.__vNode.data.hook = {create: () => {componentInstance.componentDidMount()}}}//如果是函数组件,那么调用它,并返回执行结果if (typeof (type) == 'function') {return type(props);}return h(type, { props }, children); }; ... export default QndReact; 至此,我们已经在类组件上支持了 componentDidMount 生命周期钩子函数 。
结束之前,我们再添加下事件绑定的支持 。为此,我们可以在 Counter 组件中增加一个按钮,点击的时候,计数器的数字增加 。请注意,我们遵循的是基于常规的JS事件命名约定,而非基于 React,即双击事件使用 onDblClick,而非 onDoubleClick 。
import QndReact from './qnd-react'; export default class Counter extends QndReact.Component {constructor(props) {super(props);this.state = {count: 0}}componentDidMount() {console.log('Component mounted');}render() {return (<div><p>Count: {this.state.count}</p><button onClick={() => this.setState({count: this.state.count + 1})}>Increment</button></div>)} } 上面的组件不会正常工作,因为我们没有告诉我们的 VDom 如何去处理它 。首先,我们给 Snabdom 增加事件监听模块 。
//src/qnd-react-dom.js import QndReact from './qnd-react'; import * as snabbdom from 'snabbdom'; import propsModule from 'snabbdom/modules/props'; import eventlistenersModule from 'snabbdom/modules/eventlisteners'; const reconcile = snabbdom.init([propsModule, eventlistenersModule]); ... Snabdom 希望将文本属性和事件属性作为两个单独的对象,我们我们需要这样做:
//src/qnd-react.js import { h } from 'snabbdom'; const createElement = (type, props = {}, ...children) => {...let dataProps = {};let eventProps = {};for (let propKey in props) {// event 属性总是以 `on` 开头if (propKey.startsWith('on')) {const event = propKey.substring(2).toLowerCase();eventProps[event] = props[propKey];} else {dataProps[propKey] = props[propKey];}}return h(type, { props: dataProps, on: eventProps }, children); }; ...
推荐阅读
- ThinkPHP5任意代码执行分析全记录
- 五峰,多角度广思维构建现代茶产业体系 打造土家生态茶乡
- Swoole简介
- 佳能喷墨打印机报错不能用?看到这些代码这样做立马解决
- php之"EXCEL导出"代码生成器的实现思路
- 运行 JavaScript 代码片段的 20 种工具
- 博士|复旦博士写了130行代码:2分钟搞定繁琐的核酸筛查
- 十五个常见的WordPresswp-config.php设置代码
- 如何优雅的在头条插入代码,介绍几款在线源代码转图片工具
- 暴雪|魔兽正式服:玩家复原暴雪代码,10.0增加新职业,兼顾3个职责?